diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp --- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp +++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp @@ -7,7 +7,17 @@ //===----------------------------------------------------------------------===// #include "AnalysisInternal.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/TemplateName.h" +#include "clang/AST/Type.h" +#include "clang/AST/TypeLoc.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Casting.h" namespace clang::include_cleaner { namespace { @@ -16,6 +26,16 @@ class ASTWalker : public RecursiveASTVisitor { DeclCallback Callback; + bool handleTemplateName(SourceLocation Loc, TemplateName TN) { + // For using-templates, only mark the alias. + if (auto *USD = TN.getAsUsingShadowDecl()) { + report(Loc, USD); + return true; + } + report(Loc, TN.getAsTemplateDecl()); + return true; + } + void report(SourceLocation Loc, NamedDecl *ND) { if (!ND || Loc.isInvalid()) return; @@ -25,15 +45,91 @@ public: ASTWalker(DeclCallback Callback) : Callback(Callback) {} + bool VisitDeclRefExpr(DeclRefExpr *DRE) { + report(DRE->getLocation(), DRE->getFoundDecl()); + return true; + } + + bool VisitMemberExpr(MemberExpr *E) { + report(E->getMemberLoc(), E->getFoundDecl().getDecl()); + return true; + } + + bool VisitCXXConstructExpr(CXXConstructExpr *E) { + report(E->getLocation(), E->getConstructor()); + return true; + } + + bool VisitOverloadExpr(OverloadExpr *E) { + // Since we can't prove which overloads are used, report all of them. + // FIXME: Provide caller with the ability to make a decision for such uses. + llvm::for_each(E->decls(), + [this, E](NamedDecl *D) { report(E->getNameLoc(), D); }); + return true; + } + + bool VisitUsingDecl(UsingDecl *UD) { + // FIXME: Provide caller with the ability to tell apart used/non-used + // targets. + for (const auto *Shadow : UD->shadows()) + report(UD->getLocation(), Shadow->getTargetDecl()); + return true; + } + + bool VisitFunctionDecl(FunctionDecl *FD) { + // Mark declaration from definition as it needs type-checking. + if (FD->isThisDeclarationADefinition()) + report(FD->getLocation(), FD); + return true; + } + + bool VisitEnumDecl(EnumDecl *D) { + // Definition of an enum with an underlying type references declaration for + // type-checking purposes. + if (D->isThisDeclarationADefinition() && D->getIntegerTypeSourceInfo()) + report(D->getLocation(), D); + return true; + } + + // TypeLoc visitors. + bool VisitUsingTypeLoc(UsingTypeLoc TL) { + report(TL.getNameLoc(), TL.getFoundDecl()); + return true; + } + bool VisitTagTypeLoc(TagTypeLoc TTL) { report(TTL.getNameLoc(), TTL.getDecl()); return true; } - bool VisitDeclRefExpr(DeclRefExpr *DRE) { - report(DRE->getLocation(), DRE->getFoundDecl()); + bool VisitTypedefTypeLoc(TypedefTypeLoc TTL) { + report(TTL.getNameLoc(), TTL.getTypedefNameDecl()); return true; } + + bool VisitTemplateSpecializationTypeLoc(TemplateSpecializationTypeLoc TL) { + // FIXME: Handle explicit specializations. + return handleTemplateName(TL.getTemplateNameLoc(), + TL.getTypePtr()->getTemplateName()); + } + + bool VisitDeducedTemplateSpecializationTypeLoc( + DeducedTemplateSpecializationTypeLoc TL) { + // FIXME: Handle specializations. + return handleTemplateName(TL.getTemplateNameLoc(), + TL.getTypePtr()->getTemplateName()); + } + + bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &TL) { + auto &Arg = TL.getArgument(); + // Template-template parameters require special attention, as there's no + // TemplateNameLoc. + if (Arg.getKind() == TemplateArgument::Template || + Arg.getKind() == TemplateArgument::TemplateExpansion) + return handleTemplateName(TL.getLocation(), + Arg.getAsTemplateOrTemplatePattern()); + return RecursiveASTVisitor::TraverseTemplateArgumentLoc(TL); + } }; } // namespace diff --git a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp --- a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp @@ -31,9 +31,9 @@ void foo(); namespace std { class vector {}; })cpp"); llvm::Annotations Code(R"cpp( - void bar() { + void $bar^bar() { $foo^foo(); - std::$vector^vector v; + std::$vector^vector $vconstructor^v; } )cpp"); TestInputs Inputs(Code.code()); @@ -55,14 +55,16 @@ EXPECT_EQ(FID, SM.getMainFileID()); OffsetToProviders.try_emplace(Offset, Providers.vec()); }); - auto HeaderFile = AST.fileManager().getFile("header.h").get(); + auto HeaderFile = Header(AST.fileManager().getFile("header.h").get()); + auto MainFile = Header(SM.getFileEntryForID(SM.getMainFileID())); + auto VectorSTL = Header(tooling::stdlib::Header::named("").value()); EXPECT_THAT( OffsetToProviders, UnorderedElementsAre( - Pair(Code.point("foo"), UnorderedElementsAre(Header(HeaderFile))), - Pair(Code.point("vector"), - UnorderedElementsAre(Header( - tooling::stdlib::Header::named("").value()))))); + Pair(Code.point("bar"), UnorderedElementsAre(MainFile)), + Pair(Code.point("foo"), UnorderedElementsAre(HeaderFile)), + Pair(Code.point("vector"), UnorderedElementsAre(VectorSTL)), + Pair(Code.point("vconstructor"), UnorderedElementsAre(VectorSTL)))); } } // namespace diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp --- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp @@ -38,6 +38,7 @@ Inputs.ExtraFiles["target.h"] = Target.code().str(); Inputs.ExtraArgs.push_back("-include"); Inputs.ExtraArgs.push_back("target.h"); + Inputs.ExtraArgs.push_back("-std=c++17"); TestAST AST(Inputs); const auto &SM = AST.sourceManager(); @@ -97,6 +98,8 @@ testWalk("struct S { static int ^x; };", "int y = S::^x;"); // Canonical declaration only. testWalk("extern int ^x; int x;", "int y = ^x;"); + // Return type of `foo` isn't used. + testWalk("struct S{}; S ^foo();", "auto bar() { return ^foo(); }"); } TEST(WalkAST, TagType) { @@ -111,11 +114,66 @@ using ns::^x; )cpp", "int y = ^x;"); + testWalk("using ^foo = int;", "^foo x;"); + testWalk("struct S {}; using ^foo = S;", "^foo x;"); +} + +TEST(WalkAST, Using) { + testWalk("namespace ns { void ^x(); void ^x(int); }", "using ns::^x;"); + testWalk("namespace ns { struct S; } using ns::^S;", "^S *s;"); +} + +TEST(WalkAST, Namespaces) { + testWalk("namespace ns { void x(); }", "using namespace ^ns;"); +} + +TEST(WalkAST, TemplateNames) { + testWalk("template struct ^S {};", "^S s;"); + // FIXME: Template decl has the wrong primary location for type-alias template + // decls. testWalk(R"cpp( - namespace ns { struct S; } // Not used - using ns::S; // FIXME: S should be used - )cpp", - "^S *x;"); + template struct S {}; + template ^using foo = S;)cpp", + "^foo x;"); + testWalk(R"cpp( + namespace ns {template struct S {}; } + using ns::^S;)cpp", + "^S x;"); + testWalk("template struct ^S {};", + R"cpp( + template