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,50 +58,12 @@ return S; } -// 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 -// template. This is a heuristic because the template could potentially -// have specializations that declare different members. -// Multiple declarations could be returned if the name is overloaded -// (e.g. an overloaded method in the primary template). -// This heuristic will give the desired answer in many cases, e.g. -// for a call to vector::size(). -// The name to look up is provided in the form of a factory that takes -// 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. +// Forward declaration, needed as this function is mutually recursive +// with getPointeeType() and resolveDependentExprToDecls(). std::vector getMembersReferencedViaDependentName( - const Type *T, + const Expr *E, const Type *T, llvm::function_ref NameFactory, - bool IsNonstaticMember) { - if (!T) - return {}; - if (auto *ET = T->getAs()) { - auto Result = - ET->getDecl()->lookup(NameFactory(ET->getDecl()->getASTContext())); - return {Result.begin(), Result.end()}; - } - if (auto *ICNT = T->getAs()) { - T = ICNT->getInjectedSpecializationType().getTypePtrOrNull(); - } - auto *TST = T->getAs(); - if (!TST) - return {}; - const ClassTemplateDecl *TD = dyn_cast_or_null( - TST->getTemplateName().getAsTemplateDecl()); - if (!TD) - return {}; - CXXRecordDecl *RD = TD->getTemplatedDecl(); - if (!RD->hasDefinition()) - return {}; - RD = RD->getDefinition(); - DeclarationName Name = NameFactory(RD->getASTContext()); - return RD->lookupDependentName(Name, [=](const NamedDecl *D) { - return IsNonstaticMember ? D->isCXXInstanceMember() - : !D->isCXXInstanceMember(); - }); -} + bool IsNonstaticMember); // 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 @@ -119,7 +81,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); }, @@ -144,6 +106,119 @@ return FirstArg.getAsType().getTypePtrOrNull(); } +// Try to heuristically resolve a dependent expression `E` to one +// or more declarations that it likely references. +std::vector resolveDependentExprToDecls(const Expr *E) { + switch (E->getStmtClass()) { + case Stmt::CXXDependentScopeMemberExprClass: { + const auto *ME = llvm::cast(E); + const Type *BaseType = ME->getBaseType().getTypePtrOrNull(); + if (ME->isArrow()) { + BaseType = getPointeeType(BaseType); + } + Expr *Base = ME->isImplicitAccess() ? nullptr : ME->getBase(); + return getMembersReferencedViaDependentName( + Base, BaseType, [ME](ASTContext &) { return ME->getMember(); }, + /*IsNonstaticMember=*/true); + } + case Stmt::DependentScopeDeclRefExprClass: { + const auto *RE = llvm::cast(E); + return getMembersReferencedViaDependentName( + nullptr, RE->getQualifier()->getAsType(), + [RE](ASTContext &) { return RE->getDeclName(); }, + /*IsNonstaticMember=*/false); + } + default: + return {}; + } +} + +// 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; +} + +// 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. +CXXRecordDecl *resolveTypeToRecordDecl(const Type *T) { + if (auto *RT = T->getAs()) { + return dyn_cast(RT->getDecl()); + } + + if (auto *ICNT = T->getAs()) { + T = ICNT->getInjectedSpecializationType().getTypePtrOrNull(); + } + auto *TST = T->getAs(); + if (!TST) + return nullptr; + const ClassTemplateDecl *TD = dyn_cast_or_null( + TST->getTemplateName().getAsTemplateDecl()); + if (!TD) + return nullptr; + return TD->getTemplatedDecl(); +} + +// 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 +// template. This is a heuristic because the template could potentially +// have specializations that declare different members. +// Multiple declarations could be returned if the name is overloaded +// (e.g. an overloaded method in the primary template). +// This heuristic will give the desired answer in many cases, e.g. +// for a call to vector::size(). +// The name to look up is provided in the form of a factory that takes +// 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 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())); + return {Result.begin(), Result.end()}; + } + if (auto *RD = resolveTypeToRecordDecl(T)) { + if (!RD->hasDefinition()) + return {}; + RD = RD->getDefinition(); + DeclarationName Name = NameFactory(RD->getASTContext()); + return RD->lookupDependentName(Name, [=](const NamedDecl *D) { + return IsNonstaticMember ? D->isCXXInstanceMember() + : !D->isCXXInstanceMember(); + }); + } + return {}; +} + const NamedDecl *getTemplatePattern(const NamedDecl *D) { if (const CXXRecordDecl *CRD = dyn_cast(D)) { if (const auto *Result = CRD->getTemplateInstantiationPattern()) @@ -341,21 +416,12 @@ } void VisitCXXDependentScopeMemberExpr(const CXXDependentScopeMemberExpr *E) { - const Type *BaseType = E->getBaseType().getTypePtrOrNull(); - if (E->isArrow()) { - BaseType = getPointeeType(BaseType); - } - for (const NamedDecl *D : getMembersReferencedViaDependentName( - BaseType, [E](ASTContext &) { return E->getMember(); }, - /*IsNonstaticMember=*/true)) { + for (const NamedDecl *D : resolveDependentExprToDecls(E)) { Outer.add(D, Flags); } } void VisitDependentScopeDeclRefExpr(const DependentScopeDeclRefExpr *E) { - for (const NamedDecl *D : getMembersReferencedViaDependentName( - E->getQualifier()->getAsType(), - [E](ASTContext &) { return E->getDeclName(); }, - /*IsNonstaticMember=*/false)) { + for (const NamedDecl *D : resolveDependentExprToDecls(E)) { Outer.add(D, Flags); } } 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(