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,16 +7,19 @@ //===----------------------------------------------------------------------===// #include "AnalysisInternal.h" +#include "clang-include-cleaner/Types.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/TemplateBase.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/ADT/STLFunctionalExtras.h" #include "llvm/Support/Casting.h" namespace clang::include_cleaner { @@ -62,6 +65,24 @@ return resolveTemplateName(TST->getTemplateName()); return Base->getAsRecordDecl(); } + // Templated as TemplateSpecializationType and + // DeducedTemplateSpecializationType doesn't share a common base. + template + // Picks the most specific specialization for a + // (Deduced)TemplateSpecializationType, while prioritizing using-decls. + NamedDecl *getMostRelevantTemplatePattern(const T *TST) { + // This is the underlying decl used by TemplateSpecializationType, can be + // null when type is dependent. + auto *RD = TST->getAsTagDecl(); + auto *ND = resolveTemplateName(TST->getTemplateName()); + // In case of exported template names always prefer the using-decl. This + // implies we'll point at the using-decl even when there's an explicit + // specializaiton using the exported name, but that's rare. + if (llvm::isa_and_present(ND)) + return ND; + // Fallback to primary template for dependent instantiations. + return RD ? RD : ND; + } public: ASTWalker(DeclCallback Callback) : Callback(Callback) {} @@ -161,17 +182,15 @@ } bool VisitTemplateSpecializationTypeLoc(TemplateSpecializationTypeLoc TL) { - // FIXME: Handle explicit specializations. report(TL.getTemplateNameLoc(), - resolveTemplateName(TL.getTypePtr()->getTemplateName())); + getMostRelevantTemplatePattern(TL.getTypePtr())); return true; } bool VisitDeducedTemplateSpecializationTypeLoc( DeducedTemplateSpecializationTypeLoc TL) { - // FIXME: Handle specializations. report(TL.getTemplateNameLoc(), - resolveTemplateName(TL.getTypePtr()->getTemplateName())); + getMostRelevantTemplatePattern(TL.getTypePtr())); return true; } 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 @@ -114,6 +114,25 @@ // One explicit call from the TypeLoc in constructor spelling, another // implicit reference through the constructor call. testWalk("struct $explicit^$implicit^S { static int x; };", "auto y = ^S();"); + testWalk("template struct $explicit^Foo {};", "^Foo x;"); + testWalk(R"cpp( + template struct Foo {}; + template<> struct $explicit^Foo {};)cpp", + "^Foo x;"); + testWalk(R"cpp( + template struct Foo {}; + template struct $explicit^Foo { void x(); };)cpp", + "^Foo x;"); + testWalk(R"cpp( + template struct Foo {}; + template struct $explicit^Foo;)cpp", + "^Foo x;"); + // FIXME: This is broken due to + // https://github.com/llvm/llvm-project/issues/42259. + testWalk(R"cpp( + template struct $explicit^Foo { Foo(T); }; + template<> struct Foo { void get(); Foo(int); };)cpp", + "^Foo x(3);"); } TEST(WalkAST, Alias) { @@ -124,6 +143,25 @@ "int y = ^x;"); testWalk("using $explicit^foo = int;", "^foo x;"); testWalk("struct S {}; using $explicit^foo = S;", "^foo x;"); + testWalk(R"cpp( + template struct Foo {}; + template<> struct Foo {}; + namespace ns { using ::$explicit^Foo; })cpp", + "ns::^Foo x;"); + testWalk(R"cpp( + template struct Foo {}; + namespace ns { using ::Foo; } + template<> struct ns::$explicit^Foo {};)cpp", + "^Foo x;"); + // AST doesn't have enough information to figure out whether specialization + // happened through an exported type or not. So err towards attributing use to + // the using-decl, specializations on the exported type should be rare and + // they're not permitted on type-aliases. + testWalk(R"cpp( + template struct Foo {}; + namespace ns { using ::$explicit^Foo; } + template<> struct ns::Foo {};)cpp", + "ns::^Foo x;"); } TEST(WalkAST, Using) { @@ -183,10 +221,6 @@ template