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 @@ -20,8 +20,13 @@ //===----------------------------------------------------------------------===// #include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/NestedNameSpecifier.h" +#include "clang/AST/Stmt.h" #include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" #include @@ -69,6 +74,47 @@ llvm::SmallVector targetDecl(const ast_type_traits::DynTypedNode &, DeclRelationSet Mask); +/// Information about a reference written in the source code, independent of the +/// actual AST node that this reference lives in. +/// Useful for tools that are source-aware, e.g. refactorings. +struct ReferenceLoc { + /// Contains qualifier written in the code, if any, e.g. 'ns::' for 'ns::foo'. + NestedNameSpecifierLoc Qualifier; + /// Start location of the last name part, i.e. 'foo' in 'ns::foo'. + SourceLocation NameLoc; + // FIXME: add info about template arguments. + /// A list of targets referenced by this name. Normally this has a single + /// element, but multiple is also possible, e.g. in case of using declarations + /// or unresolved overloaded functions. + /// For dependent and unresolved references, Targets can also be empty. + llvm::SmallVector Targets; + /// If reference comes from a MemberExpr, the LHS of the reference, e.g. 'obj' + /// in 'obj.foo'. + const Expr* MemberExprBase = nullptr; +}; + +/// Obtain information about a reference in \p N, if any. Note that any of the +/// fields in the returned structure can be empty, but not all of them, e.g. +/// - for implicitly generated nodes (e.g. MemberExpr from range-based-for), +/// source location information may be missing, +/// - for dependent code, targets may be empty. +/// +/// (!) For the purposes of this function declarations are not considered to be +/// references. However, declarations can have:wa references inside them, +/// e.g. 'namespace foo = std' references namespace 'std' and this function +/// will return the corresponding reference. +llvm::Optional +explicitReference(ast_type_traits::DynTypedNode N); + +/// Recursively traverse \p S and report all references explicitly written in +/// the code. The main use-case is refactorings that need to process all +/// references in some subrange of the file and apply simple edits, e.g. add +/// qualifiers. +/// FIXME: currently this does not report references to overloaded operators. +/// FIXME: extend to report location information about declaration names too. +void findExplicitReferences(Stmt *S, + llvm::function_ref Out); + /// 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 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 @@ -10,22 +10,31 @@ #include "AST.h" #include "Logger.h" #include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/DeclVisitor.h" #include "clang/AST/DeclarationName.h" +#include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" +#include "clang/AST/NestedNameSpecifier.h" +#include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/StmtVisitor.h" #include "clang/AST/Type.h" +#include "clang/AST/TypeLoc.h" #include "clang/AST/TypeLocVisitor.h" #include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/raw_ostream.h" +#include namespace clang { namespace clangd { namespace { +using ast_type_traits::DynTypedNode; LLVM_ATTRIBUTE_UNUSED std::string nodeToString(const ast_type_traits::DynTypedNode &N) { @@ -348,12 +357,246 @@ llvm::SmallVector targetDecl(const ast_type_traits::DynTypedNode &N, DeclRelationSet Mask) { llvm::SmallVector Result; - for (const auto &Entry : allTargetDecls(N)) + for (const auto &Entry : allTargetDecls(N)) { if (!(Entry.second & ~Mask)) Result.push_back(Entry.first); + } return Result; } +namespace { +llvm::SmallVector +explicitReferenceTargets(ast_type_traits::DynTypedNode N, + DeclRelationSet Mask = {}) { + assert(!(Mask & (DeclRelation::TemplatePattern | + DeclRelation::TemplateInstantiation)) && + "namedTarget handles templates on its own"); + auto Decls = allTargetDecls(N); + + // We prefer to return template instantiation, but fallback to template + // pattern if instantiation is not available. + Mask |= DeclRelation::TemplatePattern | DeclRelation::TemplateInstantiation; + bool SeenTemplateInstantiation = + llvm::find_if(Decls, [&](std::pair D) { + if (D.second & ~Mask) + return false; + return static_cast(D.second & + DeclRelation::TemplateInstantiation); + }) != Decls.end(); + if (SeenTemplateInstantiation) + Mask &= ~DeclRelation::TemplatePattern; + + llvm::SmallVector Named; + for (auto &D : Decls) { + if (D.second & ~Mask) + continue; + Named.push_back(llvm::cast(D.first)); + } + return Named; +} + +Optional refInDecl(const Decl *D) { + struct Visitor : ConstDeclVisitor { + llvm::Optional Ref; + + void VisitUsingDirectiveDecl(const UsingDirectiveDecl *D) { + Ref = ReferenceLoc{D->getQualifierLoc(), + D->getIdentLocation(), + {D->getNominatedNamespaceAsWritten()}}; + } + + void VisitUsingDecl(const UsingDecl *D) { + Ref = ReferenceLoc{D->getQualifierLoc(), D->getUsingLoc(), + explicitReferenceTargets(DynTypedNode::create(*D), + DeclRelation::Underlying)}; + } + + void VisitNamespaceAliasDecl(const NamespaceAliasDecl *D) { + Ref = ReferenceLoc{D->getQualifierLoc(), + D->getTargetNameLoc(), + {D->getAliasedNamespace()}}; + } + }; + + Visitor V; + V.Visit(D); + return V.Ref; +} + +Optional refInExpr(const Expr *E) { + struct Visitor : ConstStmtVisitor { + llvm::Optional Ref; + + void VisitDeclRefExpr(const DeclRefExpr *E) { + Ref = ReferenceLoc{ + E->getQualifierLoc(), E->getNameInfo().getLoc(), {E->getDecl()}}; + } + + void VisitMemberExpr(const MemberExpr *E) { + Ref = ReferenceLoc{E->getQualifierLoc(), + E->getMemberNameInfo().getLoc(), + {E->getMemberDecl()}, + E->getBase()}; + } + }; + + Visitor V; + V.Visit(E); + return V.Ref; +} + +Optional refInTypeLoc(TypeLoc L) { + struct Visitor : TypeLocVisitor { + llvm::Optional Ref; + + void VisitElaboratedTypeLoc(ElaboratedTypeLoc L) { + // We only know about qualifier, rest if filled by inner locations. + Visit(L.getNamedTypeLoc().getUnqualifiedLoc()); + // Fill in the qualifier. + if (!Ref) + return; + assert(!Ref->Qualifier.hasQualifier() && "qualifier already set"); + Ref->Qualifier = L.getQualifierLoc(); + } + + void VisitDeducedTemplateSpecializationTypeLoc( + DeducedTemplateSpecializationTypeLoc L) { + Ref = ReferenceLoc{ + NestedNameSpecifierLoc(), L.getNameLoc(), + explicitReferenceTargets(DynTypedNode::create(L.getType()))}; + } + + void VisitTagTypeLoc(TagTypeLoc L) { + Ref = + ReferenceLoc{NestedNameSpecifierLoc(), L.getNameLoc(), {L.getDecl()}}; + } + + void VisitTemplateSpecializationTypeLoc(TemplateSpecializationTypeLoc L) { + Ref = ReferenceLoc{ + NestedNameSpecifierLoc(), L.getTemplateNameLoc(), + explicitReferenceTargets(DynTypedNode::create(L.getType()))}; + } + + void VisitDependentTemplateSpecializationTypeLoc( + DependentTemplateSpecializationTypeLoc L) { + Ref = ReferenceLoc{ + L.getQualifierLoc(), L.getTemplateNameLoc(), + explicitReferenceTargets(DynTypedNode::create(L.getType()))}; + } + + void VisitDependentNameTypeLoc(DependentNameTypeLoc L) { + Ref = ReferenceLoc{ + L.getQualifierLoc(), L.getNameLoc(), + explicitReferenceTargets(DynTypedNode::create(L.getType()))}; + } + + void VisitTypedefTypeLoc(TypedefTypeLoc L) { + Ref = ReferenceLoc{ + NestedNameSpecifierLoc(), L.getNameLoc(), {L.getTypedefNameDecl()}}; + } + }; + + Visitor V; + V.Visit(L.getUnqualifiedLoc()); + return V.Ref; +} + +} // namespace + +llvm::Optional +explicitReference(ast_type_traits::DynTypedNode N) { + if (auto *D = N.get()) + return refInDecl(D); + if (auto *E = N.get()) + return refInExpr(E); + if (auto *NNSL = N.get()) + return ReferenceLoc{NNSL->getPrefix(), NNSL->getLocalBeginLoc(), + explicitReferenceTargets(DynTypedNode::create( + *NNSL->getNestedNameSpecifier()))}; + if (const TypeLoc *TL = N.get()) + return refInTypeLoc(*TL); + if (const CXXCtorInitializer *CCI = N.get()) { + if (CCI->isBaseInitializer()) + return refInTypeLoc(CCI->getBaseClassLoc()); + assert(CCI->isAnyMemberInitializer()); + return ReferenceLoc{NestedNameSpecifierLoc(), + CCI->getMemberLocation(), + {CCI->getAnyMember()}}; + } + // Some nodes are lacking location information. + return llvm::None; +} + +namespace { + +class ExplicitReferenceColletor + : public RecursiveASTVisitor { +public: + ExplicitReferenceColletor(llvm::function_ref Out) + : Out(Out) { + assert(Out); + } + + bool VisitTypeLoc(TypeLoc TTL) { + if (TypeLocsToSkip.count(TTL.getBeginLoc().getRawEncoding())) + return true; + visitNode(DynTypedNode::create(TTL)); + return true; + } + + bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc L) { + // ElaboratedTypeLoc will reports information for its inner type loc. + // Otherwise we loose information about inner types loc's qualifier. + TypeLoc Inner = L.getNamedTypeLoc().getUnqualifiedLoc(); + TypeLocsToSkip.insert(Inner.getBeginLoc().getRawEncoding()); + return RecursiveASTVisitor::TraverseElaboratedTypeLoc(L); + } + + bool VisitExpr(Expr *E) { + visitNode(DynTypedNode::create(*E)); + return true; + } + + bool VisitDecl(Decl *D) { + visitNode(DynTypedNode::create(*D)); + return true; + } + + // We have to use Traverse* because there is no corresponding Visit*. + bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc L) { + if (!L.getNestedNameSpecifier()) + return true; + visitNode(DynTypedNode::create(L)); + // Inner type is missing information about its qualifier, skip it. + if (auto TL = L.getTypeLoc()) + TypeLocsToSkip.insert(TL.getBeginLoc().getRawEncoding()); + return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(L); + } + +private: + void visitNode(DynTypedNode N) { + auto Ref = explicitReference(N); + if (!Ref) + return; + // FIXME: this should be done by nodeReference. + if (Ref->NameLoc.isInvalid() || Ref->NameLoc.isMacroID()) + return; + Out(*Ref); + } + + llvm::function_ref Out; + /// TypeLocs starting at these locations must be skipped, see + /// TraverseElaboratedTypeSpecifierLoc for details. + llvm::DenseSet TypeLocsToSkip; +}; +} // namespace + +void findExplicitReferences(Stmt *S, + llvm::function_ref Out) { + assert(S); + ExplicitReferenceColletor(Out).TraverseStmt(S); +} + llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, DeclRelation R) { switch (R) { #define REL_CASE(X) \