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 @@ -24,6 +24,7 @@ #include "clang/Basic/Specifiers.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLFunctionalExtras.h" +#include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" @@ -34,11 +35,18 @@ class ASTWalker : public RecursiveASTVisitor { DeclCallback Callback; + // A flag indicating whether we are in the visiting-implicit-instantiations + // mode, controlling the RAV behavior. + bool VisitTemplateIntantiations = false; void report(SourceLocation Loc, NamedDecl *ND, RefType RT = RefType::Explicit) { if (!ND || Loc.isInvalid()) return; + // If we're traversing implicit instantiations, all reported references are + // implicit regardlessly. + if (VisitTemplateIntantiations) + RT = RefType::Implicit; Callback(Loc, *cast(ND->getCanonicalDecl()), RT); } @@ -101,6 +109,71 @@ public: ASTWalker(DeclCallback Callback) : Callback(Callback) {} + bool shouldVisitTemplateInstantiations() const { + return VisitTemplateIntantiations; + } + // We traverse the implicit template instantiations if they are inside of the + // main file. References from template instantiations are always implicit, + // as they are not spelled in the source code. + template + bool traverseImplicitInstantiations(const T *TemplateDecl, + T2 GetSpecializationKind) { + auto &SM = TemplateDecl->getASTContext().getSourceManager(); + for (auto *Spec : TemplateDecl->specializations()) { + for (auto *RD : Spec->redecls()) { + if (!SM.isWrittenInMainFile(SM.getExpansionLoc(RD->getLocation()))) + continue; + auto SpecKind = GetSpecializationKind(RD); + if (SpecKind != TSK_Undeclared && SpecKind != TSK_ImplicitInstantiation) + continue; + auto Restore = llvm::make_scope_exit( + [this]() { VisitTemplateIntantiations = false; }); + VisitTemplateIntantiations = true; + if (!TraverseDecl(Spec)) + return false; + } + } + return false; + } + bool TraverseClassTemplateDecl(ClassTemplateDecl *D) { + if (!RecursiveASTVisitor::TraverseClassTemplateDecl(D)) + return false; + return traverseImplicitInstantiations(D, [](Decl *D) { + return llvm::cast(D) + ->getSpecializationKind(); + }); + } + bool TraverseVarTemplateDecl(VarTemplateDecl *D) { + if (!RecursiveASTVisitor::TraverseVarTemplateDecl(D)) + return false; + return traverseImplicitInstantiations(D, [](Decl *D) { + return llvm::cast(D) + ->getSpecializationKind(); + }); + } + bool TraverseFunctionTemplateDecl(FunctionTemplateDecl *D) { + if (!RecursiveASTVisitor::TraverseFunctionTemplateDecl(D)) + return false; + return traverseImplicitInstantiations(D, [](FunctionDecl *FD) { + return FD->getTemplateSpecializationKind(); + }); + } + + bool TraverseLambdaExpr(LambdaExpr *LE) { + if (!RecursiveASTVisitor::TraverseLambdaExpr(LE)) + return false; + // By default, lambda class decl is not traversed by default (unless + // shouldVisitImplicitCode is true), here we traverse the operator call + // operator instantiations manually. + if (const FunctionTemplateDecl *CallOperatorMember = + LE->getLambdaClass()->getDependentLambdaCallOperator()) { + return traverseImplicitInstantiations( + CallOperatorMember, + [](FunctionDecl *FD) { return FD->getTemplateSpecializationKind(); }); + } + return true; + } + // Operators are almost always ADL extension points and by design references // to them doesn't count as uses (generally the type should provide them, so // ignore them). 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 @@ -510,5 +510,58 @@ testWalk("enum class E : int {};", "enum class ^E : int ;"); } +TEST(WalkAST, ImplicitInstantiations) { + // Class template instantations. + testWalk(R"cpp( + struct $implicit^Foo {int a;}; + const Foo& getFoo();)cpp", + R"cpp( + template + struct Bar { + Bar(const T& t) { t.^a;}; + }; + void test() { + auto k = Bar(getFoo()); + })cpp"); + // Function template instantations. + testWalk(R"cpp( + struct $implicit^Foo {int a;}; + const Foo& getFoo();)cpp", + R"cpp( + template + int getT(const T& t) { + return t.^a; + } + + void test() { + auto k = getT(getFoo()); + })cpp"); + // Var template instantations. + testWalk(R"cpp( + struct $implicit^Foo {int a;}; + const Foo getFoo();)cpp", + R"cpp( + template + int foo = T().^a; + + void test() { + foo; + })cpp"); + + // Generic lambdas, verified the implicit instantiations of the operator call + // method are traversed. + testWalk(R"cpp( + struct $implicit^Foo {int a;}; + struct $implicit^Bar {int a;}; + const Foo& getFoo(); + const Bar& getBar();)cpp", + R"cpp( + void test() { + auto Generic = [](auto x) { x.^a; }; + Generic(getFoo()); + Generic(getBar()); + })cpp"); +} + } // namespace } // namespace clang::include_cleaner