diff --git a/clang-tools-extra/clangd/ASTSignals.cpp b/clang-tools-extra/clangd/ASTSignals.cpp --- a/clang-tools-extra/clangd/ASTSignals.cpp +++ b/clang-tools-extra/clangd/ASTSignals.cpp @@ -15,27 +15,30 @@ ASTSignals ASTSignals::derive(const ParsedAST &AST) { ASTSignals Signals; const SourceManager &SM = AST.getSourceManager(); - findExplicitReferences(AST.getASTContext(), [&](ReferenceLoc Ref) { - for (const NamedDecl *ND : Ref.Targets) { - if (!isInsideMainFile(Ref.NameLoc, SM)) - continue; - SymbolID ID = getSymbolID(ND); - if (!ID) - continue; - unsigned &SymbolCount = Signals.ReferencedSymbols[ID]; - SymbolCount++; - // Process namespace only when we see the symbol for the first time. - if (SymbolCount != 1) - continue; - if (const auto *NSD = dyn_cast(ND->getDeclContext())) { - if (NSD->isAnonymousNamespace()) - continue; - std::string NS = printNamespaceScope(*NSD); - if (!NS.empty()) - Signals.RelatedNamespaces[NS]++; - } - } - }); + findExplicitReferences( + AST.getASTContext(), + [&](ReferenceLoc Ref) { + for (const NamedDecl *ND : Ref.Targets) { + if (!isInsideMainFile(Ref.NameLoc, SM)) + continue; + SymbolID ID = getSymbolID(ND); + if (!ID) + continue; + unsigned &SymbolCount = Signals.ReferencedSymbols[ID]; + SymbolCount++; + // Process namespace only when we see the symbol for the first time. + if (SymbolCount != 1) + continue; + if (const auto *NSD = dyn_cast(ND->getDeclContext())) { + if (NSD->isAnonymousNamespace()) + continue; + std::string NS = printNamespaceScope(*NSD); + if (!NS.empty()) + Signals.RelatedNamespaces[NS]++; + } + } + }, + AST.getHeuristicResolver()); return Signals; } } // namespace clangd diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -71,6 +71,7 @@ GlobalCompilationDatabase.cpp Headers.cpp HeaderSourceSwitch.cpp + HeuristicResolver.cpp Hover.cpp IncludeFixer.cpp JSONTransport.cpp diff --git a/clang-tools-extra/clangd/FindTarget.h b/clang-tools-extra/clangd/FindTarget.h --- a/clang-tools-extra/clangd/FindTarget.h +++ b/clang-tools-extra/clangd/FindTarget.h @@ -37,6 +37,8 @@ namespace clang { namespace clangd { +class HeuristicResolver; + /// Describes the link between an AST node and a Decl it refers to. enum class DeclRelation : unsigned; /// A bitfield of DeclRelations. @@ -80,15 +82,16 @@ /// If callers want to support such decls, they should cast the node directly. /// /// FIXME: some AST nodes cannot be DynTypedNodes, these cannot be specified. -llvm::SmallVector targetDecl(const DynTypedNode &, - DeclRelationSet Mask); +llvm::SmallVector +targetDecl(const DynTypedNode &, DeclRelationSet Mask, + const HeuristicResolver *Resolver); /// Similar to targetDecl(), however instead of applying a filter, all possible /// decls are returned along with their DeclRelationSets. /// This is suitable for indexing, where everything is recorded and filtering /// is applied later. llvm::SmallVector, 1> -allTargetDecls(const DynTypedNode &); +allTargetDecls(const DynTypedNode &, const HeuristicResolver *); enum class DeclRelation : unsigned { // Template options apply when the declaration is an instantiated template. @@ -146,11 +149,14 @@ /// FIXME: currently this does not report references to overloaded operators. /// FIXME: extend to report location information about declaration names too. void findExplicitReferences(const Stmt *S, - llvm::function_ref Out); + llvm::function_ref Out, + const HeuristicResolver *Resolver); void findExplicitReferences(const Decl *D, - llvm::function_ref Out); + llvm::function_ref Out, + const HeuristicResolver *Resolver); void findExplicitReferences(const ASTContext &AST, - llvm::function_ref Out); + llvm::function_ref Out, + const HeuristicResolver *Resolver); /// Find declarations explicitly referenced in the source code defined by \p N. /// For templates, will prefer to return a template instantiation whenever @@ -162,7 +168,8 @@ /// ^~~ there is no Decl for 'Ptr', so we return the template pattern. /// \p Mask should not contain TemplatePattern or TemplateInstantiation. llvm::SmallVector -explicitReferenceTargets(DynTypedNode N, DeclRelationSet Mask); +explicitReferenceTargets(DynTypedNode N, DeclRelationSet Mask, + const HeuristicResolver *Resolver); // Boring implementation details of bitfield. 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 @@ -8,6 +8,7 @@ #include "FindTarget.h" #include "AST.h" +#include "HeuristicResolver.h" #include "support/Logger.h" #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/Decl.h" @@ -56,210 +57,6 @@ return S; } -// 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) { - assert(T); - - if (const auto *RT = T->getAs()) - return dyn_cast(RT->getDecl()); - - if (const auto *ICNT = T->getAs()) - T = ICNT->getInjectedSpecializationType().getTypePtrOrNull(); - if (!T) - return nullptr; - - const 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 tag-decl 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. -std::vector getMembersReferencedViaDependentName( - const Type *T, - llvm::function_ref NameFactory, - llvm::function_ref Filter) { - 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, Filter); - } - return {}; -} - -const auto NonStaticFilter = [](const NamedDecl *D) { - return D->isCXXInstanceMember(); -}; -const auto StaticFilter = [](const NamedDecl *D) { - return !D->isCXXInstanceMember(); -}; -const auto ValueFilter = [](const NamedDecl *D) { return isa(D); }; -const auto TypeFilter = [](const NamedDecl *D) { return isa(D); }; -const auto TemplateFilter = [](const NamedDecl *D) { - return isa(D); -}; - -// 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) { - if (!T) - return nullptr; - - if (T->isPointerType()) { - return T->getAs()->getPointeeType().getTypePtrOrNull(); - } - - // Try to handle smart pointer types. - - // Look up operator-> in the primary template. If we find one, it's probably a - // smart pointer type. - auto ArrowOps = getMembersReferencedViaDependentName( - T, - [](ASTContext &Ctx) { - return Ctx.DeclarationNames.getCXXOperatorName(OO_Arrow); - }, - NonStaticFilter); - if (ArrowOps.empty()) - return nullptr; - - // Getting the return type of the found operator-> method decl isn't useful, - // because we discarded template arguments to perform lookup in the primary - // template scope, so the return type would just have the form U* where U is a - // template parameter type. - // Instead, just handle the common case where the smart pointer type has the - // form of SmartPtr, and assume X is the pointee type. - auto *TST = T->getAs(); - if (!TST) - return nullptr; - if (TST->getNumArgs() == 0) - return nullptr; - const TemplateArgument &FirstArg = TST->getArg(0); - if (FirstArg.getKind() != TemplateArgument::Type) - return nullptr; - return FirstArg.getAsType().getTypePtrOrNull(); -} - -// Forward declaration, needed as this function is mutually recursive -// with resolveExprToDecls. -const Type *resolveExprToType(const Expr *E); - -// Try to heuristically resolve a possibly-dependent expression `E` to one -// or more declarations that it likely references. -std::vector resolveExprToDecls(const Expr *E) { - if (const auto *ME = dyn_cast(E)) { - const Type *BaseType = ME->getBaseType().getTypePtrOrNull(); - if (ME->isArrow()) { - BaseType = getPointeeType(BaseType); - } - if (!BaseType) - return {}; - if (const auto *BT = BaseType->getAs()) { - // 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 = resolveExprToType(Base); - } - } - return getMembersReferencedViaDependentName( - BaseType, [ME](ASTContext &) { return ME->getMember(); }, - NonStaticFilter); - } - if (const auto *RE = dyn_cast(E)) { - return getMembersReferencedViaDependentName( - RE->getQualifier()->getAsType(), - [RE](ASTContext &) { return RE->getDeclName(); }, StaticFilter); - } - if (const auto *CE = dyn_cast(E)) { - const auto *CalleeType = resolveExprToType(CE->getCallee()); - if (!CalleeType) - return {}; - if (const auto *FnTypePtr = CalleeType->getAs()) - CalleeType = FnTypePtr->getPointeeType().getTypePtr(); - if (const FunctionType *FnType = CalleeType->getAs()) { - if (const auto *D = - resolveTypeToRecordDecl(FnType->getReturnType().getTypePtr())) { - return {D}; - } - } - } - if (const auto *ME = dyn_cast(E)) - return {ME->getMemberDecl()}; - if (const auto *DRE = dyn_cast(E)) - return {DRE->getFoundDecl()}; - return {}; -} - -const Type *resolveDeclsToType(const std::vector &Decls) { - if (Decls.size() != 1) // Names an overload set -- just bail. - return nullptr; - if (const auto *TD = dyn_cast(Decls[0])) { - return TD->getTypeForDecl(); - } - if (const auto *VD = dyn_cast(Decls[0])) { - return VD->getType().getTypePtrOrNull(); - } - return nullptr; -} - -// Try to heuristically resolve the type of a possibly-dependent expression `E`. -const Type *resolveExprToType(const Expr *E) { - return resolveDeclsToType(resolveExprToDecls(E)); -} - -// Try to heuristically resolve the type of a possibly-dependent nested name -// specifier. -const Type *resolveNestedNameSpecifierToType(const NestedNameSpecifier *NNS) { - if (!NNS) - return nullptr; - - switch (NNS->getKind()) { - case NestedNameSpecifier::TypeSpec: - case NestedNameSpecifier::TypeSpecWithTemplate: - return NNS->getAsType(); - case NestedNameSpecifier::Identifier: { - return resolveDeclsToType(getMembersReferencedViaDependentName( - resolveNestedNameSpecifierToType(NNS->getPrefix()), - [&](const ASTContext &) { return NNS->getAsIdentifier(); }, - TypeFilter)); - } - default: - break; - } - return nullptr; -} - const NamedDecl *getTemplatePattern(const NamedDecl *D) { if (const CXXRecordDecl *CRD = dyn_cast(D)) { if (const auto *Result = CRD->getTemplateInstantiationPattern()) @@ -327,6 +124,7 @@ using Rel = DeclRelation; private: + const HeuristicResolver *Resolver; llvm::SmallDenseMap> Decls; @@ -346,6 +144,8 @@ } public: + TargetFinder(const HeuristicResolver *Resolver) : Resolver(Resolver) {} + llvm::SmallVector, 1> takeDecls() const { using ValTy = std::pair; llvm::SmallVector Result; @@ -385,11 +185,10 @@ Flags |= Rel::Alias; // continue with the alias } else if (const UnresolvedUsingValueDecl *UUVD = dyn_cast(D)) { - for (const NamedDecl *Target : getMembersReferencedViaDependentName( - UUVD->getQualifier()->getAsType(), - [UUVD](ASTContext &) { return UUVD->getNameInfo().getName(); }, - ValueFilter)) { - add(Target, Flags); // no Underlying as this is a non-renaming alias + if (Resolver) { + for (const NamedDecl *Target : Resolver->resolveUsingValueDecl(UUVD)) { + add(Target, Flags); // no Underlying as this is a non-renaming alias + } } Flags |= Rel::Alias; // continue with the alias } else if (const UsingShadowDecl *USD = dyn_cast(D)) { @@ -486,13 +285,17 @@ } void VisitCXXDependentScopeMemberExpr(const CXXDependentScopeMemberExpr *E) { - for (const NamedDecl *D : resolveExprToDecls(E)) { - Outer.add(D, Flags); + if (Outer.Resolver) { + for (const NamedDecl *D : Outer.Resolver->resolveMemberExpr(E)) { + Outer.add(D, Flags); + } } } void VisitDependentScopeDeclRefExpr(const DependentScopeDeclRefExpr *E) { - for (const NamedDecl *D : resolveExprToDecls(E)) { - Outer.add(D, Flags); + if (Outer.Resolver) { + for (const NamedDecl *D : Outer.Resolver->resolveDeclRefExpr(E)) { + Outer.add(D, Flags); + } } } void VisitObjCIvarRefExpr(const ObjCIvarRefExpr *OIRE) { @@ -571,20 +374,20 @@ Outer.add(TD->getTemplatedDecl(), Flags | Rel::TemplatePattern); } void VisitDependentNameType(const DependentNameType *DNT) { - for (const NamedDecl *ND : getMembersReferencedViaDependentName( - resolveNestedNameSpecifierToType(DNT->getQualifier()), - [DNT](ASTContext &) { return DNT->getIdentifier(); }, - TypeFilter)) { - Outer.add(ND, Flags); + if (Outer.Resolver) { + for (const NamedDecl *ND : + Outer.Resolver->resolveDependentNameType(DNT)) { + Outer.add(ND, Flags); + } } } void VisitDependentTemplateSpecializationType( const DependentTemplateSpecializationType *DTST) { - for (const NamedDecl *ND : getMembersReferencedViaDependentName( - resolveNestedNameSpecifierToType(DTST->getQualifier()), - [DTST](ASTContext &) { return DTST->getIdentifier(); }, - TemplateFilter)) { - Outer.add(ND, Flags); + if (Outer.Resolver) { + for (const NamedDecl *ND : + Outer.Resolver->resolveTemplateSpecializationType(DTST)) { + Outer.add(ND, Flags); + } } } void VisitTypedefType(const TypedefType *TT) { @@ -649,9 +452,14 @@ add(NNS->getAsNamespaceAlias(), Flags); return; case NestedNameSpecifier::Identifier: + if (Resolver) { + add(QualType(Resolver->resolveNestedNameSpecifierToType(NNS), 0), + Flags); + } + return; case NestedNameSpecifier::TypeSpec: case NestedNameSpecifier::TypeSpecWithTemplate: - add(QualType(resolveNestedNameSpecifierToType(NNS), 0), Flags); + add(QualType(NNS->getAsType(), 0), Flags); return; case NestedNameSpecifier::Global: // This should be TUDecl, but we can't get a pointer to it! @@ -690,9 +498,9 @@ } // namespace llvm::SmallVector, 1> -allTargetDecls(const DynTypedNode &N) { +allTargetDecls(const DynTypedNode &N, const HeuristicResolver *Resolver) { dlog("allTargetDecls({0})", nodeToString(N)); - TargetFinder Finder; + TargetFinder Finder(Resolver); DeclRelationSet Flags; if (const Decl *D = N.get()) Finder.add(D, Flags); @@ -715,10 +523,11 @@ return Finder.takeDecls(); } -llvm::SmallVector targetDecl(const DynTypedNode &N, - DeclRelationSet Mask) { +llvm::SmallVector +targetDecl(const DynTypedNode &N, DeclRelationSet Mask, + const HeuristicResolver *Resolver) { llvm::SmallVector Result; - for (const auto &Entry : allTargetDecls(N)) { + for (const auto &Entry : allTargetDecls(N, Resolver)) { if (!(Entry.second & ~Mask)) Result.push_back(Entry.first); } @@ -726,11 +535,12 @@ } llvm::SmallVector -explicitReferenceTargets(DynTypedNode N, DeclRelationSet Mask) { +explicitReferenceTargets(DynTypedNode N, DeclRelationSet Mask, + const HeuristicResolver *Resolver) { assert(!(Mask & (DeclRelation::TemplatePattern | DeclRelation::TemplateInstantiation)) && "explicitReferenceTargets handles templates on its own"); - auto Decls = allTargetDecls(N); + auto Decls = allTargetDecls(N, Resolver); // We prefer to return template instantiation, but fallback to template // pattern if instantiation is not available. @@ -757,8 +567,12 @@ } namespace { -llvm::SmallVector refInDecl(const Decl *D) { +llvm::SmallVector refInDecl(const Decl *D, + const HeuristicResolver *Resolver) { struct Visitor : ConstDeclVisitor { + Visitor(const HeuristicResolver *Resolver) : Resolver(Resolver) {} + + const HeuristicResolver *Resolver; llvm::SmallVector Refs; void VisitUsingDirectiveDecl(const UsingDirectiveDecl *D) { @@ -772,10 +586,10 @@ void VisitUsingDecl(const UsingDecl *D) { // "using ns::identifier;" is a non-declaration reference. - Refs.push_back( - ReferenceLoc{D->getQualifierLoc(), D->getLocation(), /*IsDecl=*/false, - explicitReferenceTargets(DynTypedNode::create(*D), - DeclRelation::Underlying)}); + Refs.push_back(ReferenceLoc{ + D->getQualifierLoc(), D->getLocation(), /*IsDecl=*/false, + explicitReferenceTargets(DynTypedNode::create(*D), + DeclRelation::Underlying, Resolver)}); } void VisitNamespaceAliasDecl(const NamespaceAliasDecl *D) { @@ -821,13 +635,17 @@ } }; - Visitor V; + Visitor V{Resolver}; V.Visit(D); return V.Refs; } -llvm::SmallVector refInStmt(const Stmt *S) { +llvm::SmallVector refInStmt(const Stmt *S, + const HeuristicResolver *Resolver) { struct Visitor : ConstStmtVisitor { + Visitor(const HeuristicResolver *Resolver) : Resolver(Resolver) {} + + const HeuristicResolver *Resolver; // FIXME: handle more complicated cases: more ObjC, designated initializers. llvm::SmallVector Refs; @@ -848,7 +666,7 @@ void VisitDependentScopeDeclRefExpr(const DependentScopeDeclRefExpr *E) { Refs.push_back(ReferenceLoc{ E->getQualifierLoc(), E->getNameInfo().getLoc(), /*IsDecl=*/false, - explicitReferenceTargets(DynTypedNode::create(*E), {})}); + explicitReferenceTargets(DynTypedNode::create(*E), {}, Resolver)}); } void VisitMemberExpr(const MemberExpr *E) { @@ -864,10 +682,10 @@ void VisitCXXDependentScopeMemberExpr(const CXXDependentScopeMemberExpr *E) { - Refs.push_back( - ReferenceLoc{E->getQualifierLoc(), E->getMemberNameInfo().getLoc(), - /*IsDecl=*/false, - explicitReferenceTargets(DynTypedNode::create(*E), {})}); + Refs.push_back(ReferenceLoc{ + E->getQualifierLoc(), E->getMemberNameInfo().getLoc(), + /*IsDecl=*/false, + explicitReferenceTargets(DynTypedNode::create(*E), {}, Resolver)}); } void VisitOverloadExpr(const OverloadExpr *E) { @@ -890,7 +708,7 @@ NestedNameSpecifierLoc(), E->getLocation(), /*IsDecl=*/false, // Select the getter, setter, or @property depending on the call. - explicitReferenceTargets(DynTypedNode::create(*E), {})}); + explicitReferenceTargets(DynTypedNode::create(*E), {}, Resolver)}); } void VisitDesignatedInitExpr(const DesignatedInitExpr *DIE) { @@ -922,13 +740,17 @@ } }; - Visitor V; + Visitor V{Resolver}; V.Visit(S); return V.Refs; } -llvm::SmallVector refInTypeLoc(TypeLoc L) { +llvm::SmallVector +refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) { struct Visitor : TypeLocVisitor { + Visitor(const HeuristicResolver *Resolver) : Resolver(Resolver) {} + + const HeuristicResolver *Resolver; llvm::Optional Ref; void VisitElaboratedTypeLoc(ElaboratedTypeLoc L) { @@ -967,14 +789,14 @@ Ref = ReferenceLoc{ NestedNameSpecifierLoc(), L.getTemplateNameLoc(), /*IsDecl=*/false, explicitReferenceTargets(DynTypedNode::create(L.getType()), - DeclRelation::Alias)}; + DeclRelation::Alias, Resolver)}; } void VisitDeducedTemplateSpecializationTypeLoc( DeducedTemplateSpecializationTypeLoc L) { Ref = ReferenceLoc{ NestedNameSpecifierLoc(), L.getNameLoc(), /*IsDecl=*/false, explicitReferenceTargets(DynTypedNode::create(L.getType()), - DeclRelation::Alias)}; + DeclRelation::Alias, Resolver)}; } void VisitInjectedClassNameTypeLoc(InjectedClassNameTypeLoc TL) { @@ -986,15 +808,16 @@ void VisitDependentTemplateSpecializationTypeLoc( DependentTemplateSpecializationTypeLoc L) { - Ref = ReferenceLoc{ - L.getQualifierLoc(), L.getTemplateNameLoc(), /*IsDecl=*/false, - explicitReferenceTargets(DynTypedNode::create(L.getType()), {})}; + Ref = ReferenceLoc{L.getQualifierLoc(), L.getTemplateNameLoc(), + /*IsDecl=*/false, + explicitReferenceTargets( + DynTypedNode::create(L.getType()), {}, Resolver)}; } void VisitDependentNameTypeLoc(DependentNameTypeLoc L) { - Ref = ReferenceLoc{ - L.getQualifierLoc(), L.getNameLoc(), /*IsDecl=*/false, - explicitReferenceTargets(DynTypedNode::create(L.getType()), {})}; + Ref = ReferenceLoc{L.getQualifierLoc(), L.getNameLoc(), /*IsDecl=*/false, + explicitReferenceTargets( + DynTypedNode::create(L.getType()), {}, Resolver)}; } void VisitTypedefTypeLoc(TypedefTypeLoc L) { @@ -1005,7 +828,7 @@ } }; - Visitor V; + Visitor V{Resolver}; V.Visit(L.getUnqualifiedLoc()); if (!V.Ref) return {}; @@ -1015,8 +838,9 @@ class ExplicitReferenceCollector : public RecursiveASTVisitor { public: - ExplicitReferenceCollector(llvm::function_ref Out) - : Out(Out) { + ExplicitReferenceCollector(llvm::function_ref Out, + const HeuristicResolver *Resolver) + : Out(Out), Resolver(Resolver) { assert(Out); } @@ -1123,19 +947,19 @@ /// function will return the corresponding reference. llvm::SmallVector explicitReference(DynTypedNode N) { if (auto *D = N.get()) - return refInDecl(D); + return refInDecl(D, Resolver); if (auto *S = N.get()) - return refInStmt(S); + return refInStmt(S, Resolver); if (auto *NNSL = N.get()) { // (!) 'DeclRelation::Alias' ensures we do not loose namespace aliases. return {ReferenceLoc{ NNSL->getPrefix(), NNSL->getLocalBeginLoc(), false, explicitReferenceTargets( DynTypedNode::create(*NNSL->getNestedNameSpecifier()), - DeclRelation::Alias)}}; + DeclRelation::Alias, Resolver)}}; } if (const TypeLoc *TL = N.get()) - return refInTypeLoc(*TL); + return refInTypeLoc(*TL, Resolver); if (const CXXCtorInitializer *CCI = N.get()) { // Other type initializers (e.g. base initializer) are handled by visiting // the typeLoc. @@ -1168,6 +992,7 @@ } llvm::function_ref Out; + const HeuristicResolver *Resolver; /// TypeLocs starting at these locations must be skipped, see /// TraverseElaboratedTypeSpecifierLoc for details. llvm::DenseSet TypeLocsToSkip; @@ -1175,18 +1000,22 @@ } // namespace void findExplicitReferences(const Stmt *S, - llvm::function_ref Out) { + llvm::function_ref Out, + const HeuristicResolver *Resolver) { assert(S); - ExplicitReferenceCollector(Out).TraverseStmt(const_cast(S)); + ExplicitReferenceCollector(Out, Resolver).TraverseStmt(const_cast(S)); } void findExplicitReferences(const Decl *D, - llvm::function_ref Out) { + llvm::function_ref Out, + const HeuristicResolver *Resolver) { assert(D); - ExplicitReferenceCollector(Out).TraverseDecl(const_cast(D)); + ExplicitReferenceCollector(Out, Resolver).TraverseDecl(const_cast(D)); } void findExplicitReferences(const ASTContext &AST, - llvm::function_ref Out) { - ExplicitReferenceCollector(Out).TraverseAST(const_cast(AST)); + llvm::function_ref Out, + const HeuristicResolver *Resolver) { + ExplicitReferenceCollector(Out, Resolver) + .TraverseAST(const_cast(AST)); } llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, DeclRelation R) { diff --git a/clang-tools-extra/clangd/HeuristicResolver.h b/clang-tools-extra/clangd/HeuristicResolver.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/HeuristicResolver.h @@ -0,0 +1,99 @@ +//===--- HeuristicResolver.h - Resolution of dependent names -----*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEURISTIC_RESOLVER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEURISTIC_RESOLVER_H + +#include "clang/AST/Decl.h" +#include "llvm/ADT/STLExtras.h" +#include + +namespace clang { + +class ASTContext; +class CallExpr; +class CXXDependentScopeMemberExpr; +class DeclarationName; +class DependentScopeDeclRefExpr; +class NamedDecl; +class Type; +class UnresolvedUsingValueDecl; + +namespace clangd { + +// This class heuristic resolution of declarations and types in template code. +// +// As a compiler, clang only needs to perform certain types of processing on +// template code (such as resolving dependent names to declarations, or +// resolving the type of a dependent expression) after instantiation. Indeed, +// C++ language features such as template specialization mean such resolution +// cannot be done accurately before instantiation +// +// However, template code is written and read in uninstantiated form, and clangd +// would like to provide editor features like go-to-definition in template code +// where possible. To this end, clangd attempts to resolve declarations and +// types in uninstantiated code by using heuristics, understanding that the +// results may not be fully accurate but that this is better than nothing. +// +// At this time, the heuristic used is a simple but effective one: assume that +// template instantiations are based on the primary template definition and not +// not a specialization. More advanced heuristics may be added in the future. +class HeuristicResolver { +public: + HeuristicResolver(ASTContext &Ctx) : Ctx(Ctx) {} + + // Try to heuristically resolve certain types of expressions, declarations, or + // types to one or more likely-referenced declarations. + std::vector + resolveMemberExpr(const CXXDependentScopeMemberExpr *ME) const; + std::vector + resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE) const; + std::vector resolveCallExpr(const CallExpr *CE) const; + std::vector + resolveUsingValueDecl(const UnresolvedUsingValueDecl *UUVD) const; + std::vector + resolveDependentNameType(const DependentNameType *DNT) const; + std::vector resolveTemplateSpecializationType( + const DependentTemplateSpecializationType *DTST) const; + + // 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 * + resolveNestedNameSpecifierToType(const NestedNameSpecifier *NNS) const; + +private: + ASTContext &Ctx; + + // Given a tag-decl 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(). + std::vector resolveDependentMember( + const Type *T, DeclarationName Name, + llvm::function_ref Filter) const; + + // Try to heuristically resolve the type of a possibly-dependent expression + // `E`. + const Type *resolveExprToType(const Expr *E) 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; +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clang-tools-extra/clangd/HeuristicResolver.cpp b/clang-tools-extra/clangd/HeuristicResolver.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/HeuristicResolver.cpp @@ -0,0 +1,225 @@ +//===--- HeuristicResolver.cpp ---------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "HeuristicResolver.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprCXX.h" + +namespace clang { +namespace clangd { + +// Convenience lambdas for use as the 'Filter' parameter of +// HeuristicResolver::resolveDependentMember(). +const auto NonStaticFilter = [](const NamedDecl *D) { + return D->isCXXInstanceMember(); +}; +const auto StaticFilter = [](const NamedDecl *D) { + return !D->isCXXInstanceMember(); +}; +const auto ValueFilter = [](const NamedDecl *D) { return isa(D); }; +const auto TypeFilter = [](const NamedDecl *D) { return isa(D); }; +const auto TemplateFilter = [](const NamedDecl *D) { + return isa(D); +}; + +// Helper function for HeuristicResolver::resolveDependentMember() +// 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) { + assert(T); + + if (const auto *RT = T->getAs()) + return dyn_cast(RT->getDecl()); + + if (const auto *ICNT = T->getAs()) + T = ICNT->getInjectedSpecializationType().getTypePtrOrNull(); + if (!T) + return nullptr; + + const 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(); +} + +const Type *HeuristicResolver::getPointeeType(const Type *T) const { + if (!T) + return nullptr; + + if (T->isPointerType()) { + return T->getAs()->getPointeeType().getTypePtrOrNull(); + } + + // Try to handle smart pointer types. + + // Look up operator-> in the primary template. If we find one, it's probably a + // smart pointer type. + auto ArrowOps = resolveDependentMember( + T, Ctx.DeclarationNames.getCXXOperatorName(OO_Arrow), NonStaticFilter); + if (ArrowOps.empty()) + return nullptr; + + // Getting the return type of the found operator-> method decl isn't useful, + // because we discarded template arguments to perform lookup in the primary + // template scope, so the return type would just have the form U* where U is a + // template parameter type. + // Instead, just handle the common case where the smart pointer type has the + // form of SmartPtr, and assume X is the pointee type. + auto *TST = T->getAs(); + if (!TST) + return nullptr; + if (TST->getNumArgs() == 0) + return nullptr; + const TemplateArgument &FirstArg = TST->getArg(0); + if (FirstArg.getKind() != TemplateArgument::Type) + return nullptr; + return FirstArg.getAsType().getTypePtrOrNull(); +} + +std::vector HeuristicResolver::resolveMemberExpr( + const CXXDependentScopeMemberExpr *ME) const { + const Type *BaseType = ME->getBaseType().getTypePtrOrNull(); + if (ME->isArrow()) { + BaseType = getPointeeType(BaseType); + } + if (!BaseType) + return {}; + if (const auto *BT = BaseType->getAs()) { + // 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 = resolveExprToType(Base); + } + } + return resolveDependentMember(BaseType, ME->getMember(), NonStaticFilter); +} + +std::vector HeuristicResolver::resolveDeclRefExpr( + const DependentScopeDeclRefExpr *RE) const { + return resolveDependentMember(RE->getQualifier()->getAsType(), + RE->getDeclName(), StaticFilter); +} + +std::vector +HeuristicResolver::resolveCallExpr(const CallExpr *CE) const { + const auto *CalleeType = resolveExprToType(CE->getCallee()); + if (!CalleeType) + return {}; + if (const auto *FnTypePtr = CalleeType->getAs()) + CalleeType = FnTypePtr->getPointeeType().getTypePtr(); + if (const FunctionType *FnType = CalleeType->getAs()) { + if (const auto *D = + resolveTypeToRecordDecl(FnType->getReturnType().getTypePtr())) { + return {D}; + } + } + return {}; +} + +std::vector HeuristicResolver::resolveUsingValueDecl( + const UnresolvedUsingValueDecl *UUVD) const { + return resolveDependentMember(UUVD->getQualifier()->getAsType(), + UUVD->getNameInfo().getName(), ValueFilter); +} + +std::vector HeuristicResolver::resolveDependentNameType( + const DependentNameType *DNT) const { + return resolveDependentMember( + resolveNestedNameSpecifierToType(DNT->getQualifier()), + DNT->getIdentifier(), TypeFilter); +} + +std::vector +HeuristicResolver::resolveTemplateSpecializationType( + const DependentTemplateSpecializationType *DTST) const { + return resolveDependentMember( + resolveNestedNameSpecifierToType(DTST->getQualifier()), + DTST->getIdentifier(), TemplateFilter); +} + +const Type *resolveDeclsToType(const std::vector &Decls) { + if (Decls.size() != 1) // Names an overload set -- just bail. + return nullptr; + if (const auto *TD = dyn_cast(Decls[0])) { + return TD->getTypeForDecl(); + } + if (const auto *VD = dyn_cast(Decls[0])) { + return VD->getType().getTypePtrOrNull(); + } + return nullptr; +} + +const Type *HeuristicResolver::resolveExprToType(const Expr *E) const { + if (const auto *ME = dyn_cast(E)) { + return resolveDeclsToType(resolveMemberExpr(ME)); + } + if (const auto *RE = dyn_cast(E)) { + return resolveDeclsToType(resolveDeclRefExpr(RE)); + } + if (const auto *CE = dyn_cast(E)) { + return resolveDeclsToType(resolveCallExpr(CE)); + } + if (const auto *ME = dyn_cast(E)) + return resolveDeclsToType({ME->getMemberDecl()}); + + return E->getType().getTypePtr(); +} + +const Type *HeuristicResolver::resolveNestedNameSpecifierToType( + const NestedNameSpecifier *NNS) const { + if (!NNS) + return nullptr; + + // The purpose of this function is to handle the dependent (Kind == + // Identifier) case, but we need to recurse on the prefix because + // that may be dependent as well, so for convenience handle + // the TypeSpec cases too. + switch (NNS->getKind()) { + case NestedNameSpecifier::TypeSpec: + case NestedNameSpecifier::TypeSpecWithTemplate: + return NNS->getAsType(); + case NestedNameSpecifier::Identifier: { + return resolveDeclsToType(resolveDependentMember( + resolveNestedNameSpecifierToType(NNS->getPrefix()), + NNS->getAsIdentifier(), TypeFilter)); + } + default: + break; + } + return nullptr; +} + +std::vector HeuristicResolver::resolveDependentMember( + const Type *T, DeclarationName Name, + llvm::function_ref Filter) const { + if (!T) + return {}; + if (auto *ET = T->getAs()) { + auto Result = ET->getDecl()->lookup(Name); + return {Result.begin(), Result.end()}; + } + if (auto *RD = resolveTypeToRecordDecl(T)) { + if (!RD->hasDefinition()) + return {}; + RD = RD->getDefinition(); + return RD->lookupDependentName(Name, Filter); + } + return {}; +} + +} // namespace clangd +} // namespace clang \ No newline at end of file diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -902,7 +902,8 @@ std::vector Result; if (const SelectionTree::Node *N = ST.commonAncestor()) { // FIXME: Fill in HighlightRange with range coming from N->ASTNode. - auto Decls = explicitReferenceTargets(N->ASTNode, DeclRelation::Alias); + auto Decls = explicitReferenceTargets(N->ASTNode, DeclRelation::Alias, + AST.getHeuristicResolver()); if (!Decls.empty()) { HI = getHoverContents(Decls.front(), PP, Index); // Layout info only shown when hovering on the field/class itself. diff --git a/clang-tools-extra/clangd/ParsedAST.h b/clang-tools-extra/clangd/ParsedAST.h --- a/clang-tools-extra/clangd/ParsedAST.h +++ b/clang-tools-extra/clangd/ParsedAST.h @@ -24,6 +24,7 @@ #include "Compiler.h" #include "Diagnostics.h" #include "Headers.h" +#include "HeuristicResolver.h" #include "Preamble.h" #include "index/CanonicalIncludes.h" #include "support/Path.h" @@ -109,6 +110,10 @@ /// AST. Might be None if no Preamble is used. llvm::Optional preambleVersion() const; + const HeuristicResolver *getHeuristicResolver() const { + return Resolver.get(); + } + private: ParsedAST(llvm::StringRef Version, std::shared_ptr Preamble, @@ -144,6 +149,7 @@ std::vector LocalTopLevelDecls; IncludeStructure Includes; CanonicalIncludes CanonIncludes; + std::unique_ptr Resolver; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -548,6 +548,7 @@ Macros(std::move(Macros)), Diags(std::move(Diags)), LocalTopLevelDecls(std::move(LocalTopLevelDecls)), Includes(std::move(Includes)), CanonIncludes(std::move(CanonIncludes)) { + Resolver = std::make_unique(getASTContext()); assert(this->Clang); assert(this->Action); } diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -535,34 +535,37 @@ // Highlight 'decltype' and 'auto' as their underlying types. CollectExtraHighlightings(Builder).TraverseAST(C); // Highlight all decls and references coming from the AST. - findExplicitReferences(C, [&](ReferenceLoc R) { - for (const NamedDecl *Decl : R.Targets) { - if (!canHighlightName(Decl->getDeclName())) - continue; - auto Kind = kindForDecl(Decl); - if (!Kind) - continue; - auto &Tok = Builder.addToken(R.NameLoc, *Kind); - - // The attribute tests don't want to look at the template. - if (auto *TD = dyn_cast(Decl)) { - if (auto *Templated = TD->getTemplatedDecl()) - Decl = Templated; - } - if (auto Mod = scopeModifier(Decl)) - Tok.addModifier(*Mod); - if (isConst(Decl)) - Tok.addModifier(HighlightingModifier::Readonly); - if (isStatic(Decl)) - Tok.addModifier(HighlightingModifier::Static); - if (isAbstract(Decl)) - Tok.addModifier(HighlightingModifier::Abstract); - if (Decl->isDeprecated()) - Tok.addModifier(HighlightingModifier::Deprecated); - if (R.IsDecl) - Tok.addModifier(HighlightingModifier::Declaration); - } - }); + findExplicitReferences( + C, + [&](ReferenceLoc R) { + for (const NamedDecl *Decl : R.Targets) { + if (!canHighlightName(Decl->getDeclName())) + continue; + auto Kind = kindForDecl(Decl); + if (!Kind) + continue; + auto &Tok = Builder.addToken(R.NameLoc, *Kind); + + // The attribute tests don't want to look at the template. + if (auto *TD = dyn_cast(Decl)) { + if (auto *Templated = TD->getTemplatedDecl()) + Decl = Templated; + } + if (auto Mod = scopeModifier(Decl)) + Tok.addModifier(*Mod); + if (isConst(Decl)) + Tok.addModifier(HighlightingModifier::Readonly); + if (isStatic(Decl)) + Tok.addModifier(HighlightingModifier::Static); + if (isAbstract(Decl)) + Tok.addModifier(HighlightingModifier::Abstract); + if (Decl->isDeprecated()) + Tok.addModifier(HighlightingModifier::Deprecated); + if (R.IsDecl) + Tok.addModifier(HighlightingModifier::Declaration); + } + }, + AST.getHeuristicResolver()); // Add highlightings for macro references. auto AddMacro = [&](const MacroOccurrence &M) { auto &T = Builder.addToken(M.Rng, HighlightingKind::Macro); diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -182,7 +182,8 @@ if (const SelectionTree::Node *N = ST.commonAncestor()) { if (NodeKind) *NodeKind = N->ASTNode.getNodeKind(); - llvm::copy_if(allTargetDecls(N->ASTNode), std::back_inserter(Result), + llvm::copy_if(allTargetDecls(N->ASTNode, AST.getHeuristicResolver()), + std::back_inserter(Result), [&](auto &Entry) { return !(Entry.second & ~Relations); }); } return !Result.empty(); @@ -496,7 +497,8 @@ } auto Decls = targetDecl(DynTypedNode::create(Type.getNonReferenceType()), - DeclRelation::TemplatePattern | DeclRelation::Alias); + DeclRelation::TemplatePattern | DeclRelation::Alias, + AST.getHeuristicResolver()); if (Decls.empty()) return {}; @@ -1213,7 +1215,8 @@ if (const SelectionTree::Node *N = ST.commonAncestor()) { DeclRelationSet Relations = DeclRelation::TemplatePattern | DeclRelation::Alias; - auto Decls = targetDecl(N->ASTNode, Relations); + auto Decls = + targetDecl(N->ASTNode, Relations, AST.getHeuristicResolver()); if (!Decls.empty()) { // FIXME: we may get multiple DocumentHighlights with the same location // and different kinds, deduplicate them. @@ -1706,7 +1709,7 @@ const CXXRecordDecl *findRecordTypeAt(ParsedAST &AST, Position Pos) { auto RecordFromNode = - [](const SelectionTree::Node *N) -> const CXXRecordDecl * { + [&AST](const SelectionTree::Node *N) -> const CXXRecordDecl * { if (!N) return nullptr; @@ -1714,7 +1717,8 @@ // instantiations and template patterns, and prefer the former if available // (generally, one will be available for non-dependent specializations of a // class template). - auto Decls = explicitReferenceTargets(N->ASTNode, DeclRelation::Underlying); + auto Decls = explicitReferenceTargets(N->ASTNode, DeclRelation::Underlying, + AST.getHeuristicResolver()); if (Decls.empty()) return nullptr; @@ -1942,13 +1946,16 @@ if (!FD->hasBody()) return {}; llvm::DenseSet DeclRefs; - findExplicitReferences(FD, [&](ReferenceLoc Ref) { - for (const Decl *D : Ref.Targets) { - if (!index::isFunctionLocalSymbol(D) && !D->isTemplateParameter() && - !Ref.IsDecl) - DeclRefs.insert(D); - } - }); + findExplicitReferences( + FD, + [&](ReferenceLoc Ref) { + for (const Decl *D : Ref.Targets) { + if (!index::isFunctionLocalSymbol(D) && !D->isTemplateParameter() && + !Ref.IsDecl) + DeclRefs.insert(D); + } + }, + AST.getHeuristicResolver()); return DeclRefs; } } // namespace clangd diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -149,7 +149,8 @@ llvm::DenseSet Result; for (const NamedDecl *D : targetDecl(SelectedNode->ASTNode, - DeclRelation::Alias | DeclRelation::TemplatePattern)) { + DeclRelation::Alias | DeclRelation::TemplatePattern, + AST.getHeuristicResolver())) { Result.insert(canonicalRenameDecl(D)); } return Result; @@ -259,16 +260,19 @@ std::vector Results; for (Decl *TopLevelDecl : AST.getLocalTopLevelDecls()) { - findExplicitReferences(TopLevelDecl, [&](ReferenceLoc Ref) { - if (Ref.Targets.empty()) - return; - for (const auto *Target : Ref.Targets) { - if (canonicalRenameDecl(Target) == &ND) { - Results.push_back(Ref.NameLoc); - return; - } - } - }); + findExplicitReferences( + TopLevelDecl, + [&](ReferenceLoc Ref) { + if (Ref.Targets.empty()) + return; + for (const auto *Target : Ref.Targets) { + if (canonicalRenameDecl(Target) == &ND) { + Results.push_back(Ref.NameLoc); + return; + } + } + }, + AST.getHeuristicResolver()); } return Results; diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp --- a/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp @@ -139,7 +139,8 @@ // Rewrites body of FD by re-spelling all of the names to make sure they are // still valid in context of Target. llvm::Expected qualifyAllDecls(const FunctionDecl *FD, - const FunctionDecl *Target) { + const FunctionDecl *Target, + const HeuristicResolver *Resolver) { // There are three types of spellings that needs to be qualified in a function // body: // - Types: Foo -> ns::Foo @@ -162,48 +163,54 @@ tooling::Replacements Replacements; bool HadErrors = false; - findExplicitReferences(FD->getBody(), [&](ReferenceLoc Ref) { - // Since we want to qualify only the first qualifier, skip names with a - // qualifier. - if (Ref.Qualifier) - return; - // There might be no decl in dependent contexts, there's nothing much we can - // do in such cases. - if (Ref.Targets.empty()) - return; - // Do not qualify names introduced by macro expansions. - if (Ref.NameLoc.isMacroID()) - return; + findExplicitReferences( + FD->getBody(), + [&](ReferenceLoc Ref) { + // Since we want to qualify only the first qualifier, skip names with a + // qualifier. + if (Ref.Qualifier) + return; + // There might be no decl in dependent contexts, there's nothing much we + // can do in such cases. + if (Ref.Targets.empty()) + return; + // Do not qualify names introduced by macro expansions. + if (Ref.NameLoc.isMacroID()) + return; - for (const NamedDecl *ND : Ref.Targets) { - if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) { - elog("define inline: Targets from multiple contexts: {0}, {1}", - printQualifiedName(*Ref.Targets.front()), printQualifiedName(*ND)); - HadErrors = true; - return; - } - } - // All Targets are in the same scope, so we can safely chose first one. - const NamedDecl *ND = Ref.Targets.front(); - // Skip anything from a non-namespace scope, these can be: - // - Function or Method scopes, which means decl is local and doesn't need - // qualification. - // - From Class/Struct/Union scope, which again doesn't need any qualifiers, - // rather the left side of it requires qualification, like: - // namespace a { class Bar { public: static int x; } } - // void foo() { Bar::x; } - // ~~~~~ -> we need to qualify Bar not x. - if (!ND->getDeclContext()->isNamespace()) - return; + for (const NamedDecl *ND : Ref.Targets) { + if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) { + elog("define inline: Targets from multiple contexts: {0}, {1}", + printQualifiedName(*Ref.Targets.front()), + printQualifiedName(*ND)); + HadErrors = true; + return; + } + } + // All Targets are in the same scope, so we can safely chose first one. + const NamedDecl *ND = Ref.Targets.front(); + // Skip anything from a non-namespace scope, these can be: + // - Function or Method scopes, which means decl is local and doesn't + // need + // qualification. + // - From Class/Struct/Union scope, which again doesn't need any + // qualifiers, + // rather the left side of it requires qualification, like: + // namespace a { class Bar { public: static int x; } } + // void foo() { Bar::x; } + // ~~~~~ -> we need to qualify Bar not x. + if (!ND->getDeclContext()->isNamespace()) + return; - const std::string Qualifier = getQualification( - FD->getASTContext(), TargetContext, Target->getBeginLoc(), ND); - if (auto Err = Replacements.add( - tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier))) { - HadErrors = true; - elog("define inline: Failed to add quals: {0}", std::move(Err)); - } - }); + const std::string Qualifier = getQualification( + FD->getASTContext(), TargetContext, Target->getBeginLoc(), ND); + if (auto Err = Replacements.add( + tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier))) { + HadErrors = true; + elog("define inline: Failed to add quals: {0}", std::move(Err)); + } + }, + Resolver); if (HadErrors) return error( @@ -230,7 +237,8 @@ /// Generates Replacements for changing template and function parameter names in /// \p Dest to be the same as in \p Source. llvm::Expected -renameParameters(const FunctionDecl *Dest, const FunctionDecl *Source) { +renameParameters(const FunctionDecl *Dest, const FunctionDecl *Source, + const HeuristicResolver *Resolver) { llvm::DenseMap ParamToNewName; llvm::DenseMap> RefLocs; auto HandleParam = [&](const NamedDecl *DestParam, @@ -286,7 +294,8 @@ if (It == ParamToNewName.end()) return; RefLocs[Target].push_back(Ref.NameLoc); - }); + }, + Resolver); // Now try to generate edits for all the refs. tooling::Replacements Replacements; @@ -451,11 +460,13 @@ return error("Couldn't find semicolon for target declaration."); auto AddInlineIfNecessary = addInlineIfInHeader(Target); - auto ParamReplacements = renameParameters(Target, Source); + auto ParamReplacements = + renameParameters(Target, Source, Sel.AST->getHeuristicResolver()); if (!ParamReplacements) return ParamReplacements.takeError(); - auto QualifiedBody = qualifyAllDecls(Source, Target); + auto QualifiedBody = + qualifyAllDecls(Source, Target, Sel.AST->getHeuristicResolver()); if (!QualifiedBody) return QualifiedBody.takeError(); diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp --- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp @@ -144,7 +144,8 @@ // FIXME: Drop attributes in function signature. llvm::Expected getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace, - const syntax::TokenBuffer &TokBuf) { + const syntax::TokenBuffer &TokBuf, + const HeuristicResolver *Resolver) { auto &AST = FD->getASTContext(); auto &SM = AST.getSourceManager(); auto TargetContext = findContextForNS(TargetNamespace, FD->getDeclContext()); @@ -156,31 +157,36 @@ // Finds the first unqualified name in function return type and name, then // qualifies those to be valid in TargetContext. - findExplicitReferences(FD, [&](ReferenceLoc Ref) { - // It is enough to qualify the first qualifier, so skip references with a - // qualifier. Also we can't do much if there are no targets or name is - // inside a macro body. - if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID()) - return; - // Only qualify return type and function name. - if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() && - Ref.NameLoc != FD->getLocation()) - return; - - for (const NamedDecl *ND : Ref.Targets) { - if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) { - elog("Targets from multiple contexts: {0}, {1}", - printQualifiedName(*Ref.Targets.front()), printQualifiedName(*ND)); - return; - } - } - const NamedDecl *ND = Ref.Targets.front(); - const std::string Qualifier = getQualification( - AST, *TargetContext, SM.getLocForStartOfFile(SM.getMainFileID()), ND); - if (auto Err = DeclarationCleanups.add( - tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier))) - Errors = llvm::joinErrors(std::move(Errors), std::move(Err)); - }); + findExplicitReferences( + FD, + [&](ReferenceLoc Ref) { + // It is enough to qualify the first qualifier, so skip references with + // a qualifier. Also we can't do much if there are no targets or name is + // inside a macro body. + if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID()) + return; + // Only qualify return type and function name. + if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() && + Ref.NameLoc != FD->getLocation()) + return; + + for (const NamedDecl *ND : Ref.Targets) { + if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) { + elog("Targets from multiple contexts: {0}, {1}", + printQualifiedName(*Ref.Targets.front()), + printQualifiedName(*ND)); + return; + } + } + const NamedDecl *ND = Ref.Targets.front(); + const std::string Qualifier = + getQualification(AST, *TargetContext, + SM.getLocForStartOfFile(SM.getMainFileID()), ND); + if (auto Err = DeclarationCleanups.add( + tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier))) + Errors = llvm::joinErrors(std::move(Errors), std::move(Err)); + }, + Resolver); // Get rid of default arguments, since they should not be specified in // out-of-line definition. @@ -421,7 +427,8 @@ return InsertionPoint.takeError(); auto FuncDef = getFunctionSourceCode( - Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens()); + Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens(), + Sel.AST->getHeuristicResolver()); if (!FuncDef) return FuncDef.takeError(); diff --git a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp --- a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp @@ -171,16 +171,19 @@ // // This performs a partial AST traversal proportional to the size of the // enclosing function, so it is possibly expensive. - bool requiresHoisting(const SourceManager &SM) const { + bool requiresHoisting(const SourceManager &SM, + const HeuristicResolver *Resolver) const { // First find all the declarations that happened inside extraction zone. llvm::SmallSet DeclsInExtZone; for (auto *RootStmt : RootStmts) { - findExplicitReferences(RootStmt, - [&DeclsInExtZone](const ReferenceLoc &Loc) { - if (!Loc.IsDecl) - return; - DeclsInExtZone.insert(Loc.Targets.front()); - }); + findExplicitReferences( + RootStmt, + [&DeclsInExtZone](const ReferenceLoc &Loc) { + if (!Loc.IsDecl) + return; + DeclsInExtZone.insert(Loc.Targets.front()); + }, + Resolver); } // Early exit without performing expensive traversal below. if (DeclsInExtZone.empty()) @@ -191,15 +194,18 @@ ZoneRange.getEnd())) continue; bool HasPostUse = false; - findExplicitReferences(S, [&](const ReferenceLoc &Loc) { - if (HasPostUse || - SM.isBeforeInTranslationUnit(Loc.NameLoc, ZoneRange.getEnd())) - return; - HasPostUse = - llvm::any_of(Loc.Targets, [&DeclsInExtZone](const Decl *Target) { - return DeclsInExtZone.contains(Target); - }); - }); + findExplicitReferences( + S, + [&](const ReferenceLoc &Loc) { + if (HasPostUse || + SM.isBeforeInTranslationUnit(Loc.NameLoc, ZoneRange.getEnd())) + return; + HasPostUse = llvm::any_of(Loc.Targets, + [&DeclsInExtZone](const Decl *Target) { + return DeclsInExtZone.contains(Target); + }); + }, + Resolver); if (HasPostUse) return true; } @@ -741,7 +747,7 @@ return false; // FIXME: Get rid of this check once we support hoisting. - if (MaybeExtZone->requiresHoisting(SM)) + if (MaybeExtZone->requiresHoisting(SM, Inputs.AST->getHeuristicResolver())) return false; ExtZone = std::move(*MaybeExtZone); diff --git a/clang-tools-extra/clangd/refactor/tweaks/RemoveUsingNamespace.cpp b/clang-tools-extra/clangd/refactor/tweaks/RemoveUsingNamespace.cpp --- a/clang-tools-extra/clangd/refactor/tweaks/RemoveUsingNamespace.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/RemoveUsingNamespace.cpp @@ -148,34 +148,37 @@ // removing the directive. std::vector IdentsToQualify; for (auto &D : Inputs.AST->getLocalTopLevelDecls()) { - findExplicitReferences(D, [&](ReferenceLoc Ref) { - if (Ref.Qualifier) - return; // This reference is already qualified. - - for (auto *T : Ref.Targets) { - if (!visibleContext(T->getDeclContext()) - ->Equals(TargetDirective->getNominatedNamespace())) - return; - } - SourceLocation Loc = Ref.NameLoc; - if (Loc.isMacroID()) { - // Avoid adding qualifiers before macro expansions, it's probably - // incorrect, e.g. - // namespace std { int foo(); } - // #define FOO 1 + foo() - // using namespace foo; // provides matrix - // auto x = FOO; // Must not changed to auto x = std::FOO - if (!SM.isMacroArgExpansion(Loc)) - return; // FIXME: report a warning to the users. - Loc = SM.getFileLoc(Ref.NameLoc); - } - assert(Loc.isFileID()); - if (SM.getFileID(Loc) != SM.getMainFileID()) - return; // FIXME: report these to the user as warnings? - if (SM.isBeforeInTranslationUnit(Loc, FirstUsingDirectiveLoc)) - return; // Directive was not visible before this point. - IdentsToQualify.push_back(Loc); - }); + findExplicitReferences( + D, + [&](ReferenceLoc Ref) { + if (Ref.Qualifier) + return; // This reference is already qualified. + + for (auto *T : Ref.Targets) { + if (!visibleContext(T->getDeclContext()) + ->Equals(TargetDirective->getNominatedNamespace())) + return; + } + SourceLocation Loc = Ref.NameLoc; + if (Loc.isMacroID()) { + // Avoid adding qualifiers before macro expansions, it's probably + // incorrect, e.g. + // namespace std { int foo(); } + // #define FOO 1 + foo() + // using namespace foo; // provides matrix + // auto x = FOO; // Must not changed to auto x = std::FOO + if (!SM.isMacroArgExpansion(Loc)) + return; // FIXME: report a warning to the users. + Loc = SM.getFileLoc(Ref.NameLoc); + } + assert(Loc.isFileID()); + if (SM.getFileID(Loc) != SM.getMainFileID()) + return; // FIXME: report these to the user as warnings? + if (SM.isBeforeInTranslationUnit(Loc, FirstUsingDirectiveLoc)) + return; // Directive was not visible before this point. + IdentsToQualify.push_back(Loc); + }, + Inputs.AST->getHeuristicResolver()); } // Remove duplicates. llvm::sort(IdentsToQualify); 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 @@ -86,7 +86,8 @@ EXPECT_EQ(N->kind(), NodeType) << Selection; std::vector ActualDecls; - for (const auto &Entry : allTargetDecls(N->ASTNode)) + for (const auto &Entry : + allTargetDecls(N->ASTNode, AST.getHeuristicResolver())) ActualDecls.emplace_back(Entry.first, Entry.second); return ActualDecls; } @@ -978,16 +979,20 @@ std::vector Refs; if (const auto *Func = llvm::dyn_cast(TestDecl)) - findExplicitReferences(Func->getBody(), [&Refs](ReferenceLoc R) { - Refs.push_back(std::move(R)); - }); + findExplicitReferences( + Func->getBody(), + [&Refs](ReferenceLoc R) { Refs.push_back(std::move(R)); }, + AST.getHeuristicResolver()); else if (const auto *NS = llvm::dyn_cast(TestDecl)) - findExplicitReferences(NS, [&Refs, &NS](ReferenceLoc R) { - // Avoid adding the namespace foo decl to the results. - if (R.Targets.size() == 1 && R.Targets.front() == NS) - return; - Refs.push_back(std::move(R)); - }); + findExplicitReferences( + NS, + [&Refs, &NS](ReferenceLoc R) { + // Avoid adding the namespace foo decl to the results. + if (R.Targets.size() == 1 && R.Targets.front() == NS) + return; + Refs.push_back(std::move(R)); + }, + AST.getHeuristicResolver()); else ADD_FAILURE() << "Failed to find ::foo decl for test";