diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -149,14 +149,9 @@ /// /// By default, this visitor tries to visit every part of the explicit /// source code exactly once. The default policy towards templates -/// is to descend into the 'pattern' class or function body, not any -/// explicit or implicit instantiations. Explicit specializations -/// are still visited, and the patterns of partial specializations -/// are visited separately. This behavior can be changed by -/// overriding shouldVisitTemplateInstantiations() in the derived class -/// to return true, in which case all known implicit and explicit -/// instantiations will be visited at the same time as the pattern -/// from which they were produced. +/// is to descend into code that was written: typically the 'pattern' +/// class or function body, but not to traverse instantiations. +/// This can be changed by overriding shouldVisitTemplateInstantiations(). /// /// By default, this visitor preorder traverses the AST. If postorder traversal /// is needed, the \c shouldTraversePostOrder method needs to be overridden @@ -174,8 +169,23 @@ /// Return a reference to the derived class. Derived &getDerived() { return *static_cast(this); } - /// Return whether this visitor should recurse into - /// template instantiations. + /// Return whether we should traverse of AST nodes that were created by + /// template instantiation, rather than by written code. + /// + /// When this is false (default), only written code is traversed: + /// - Primary template bodies are traversed: they are written code. + /// - User-defined specializations (partial or complete) are traversed too. + /// - Implicit instantiations are *not* traversed. + /// - Explicit instantiations should be partially traversed: + /// * The node itself (e.g. VarTemplateSpecializationDecl) is visited + /// * The written "head" (e.g. template args, function params) is traversed. + /// * The instantiated "body" (e.g. var initializer, class body) is not. + /// + /// When this is true, primary template bodies, user-defined specializations, + /// and both implicit and explicit instantiations are fully travesred. + /// + /// FIXME: this is the intent, but there are inconsistencies. + /// See unittests/Tooling/RecursiveASTVisitorTests/Templates.cpp bool shouldVisitTemplateInstantiations() const { return false; } /// Return whether this visitor should recurse into the types of diff --git a/clang/unittests/Tooling/CMakeLists.txt b/clang/unittests/Tooling/CMakeLists.txt --- a/clang/unittests/Tooling/CMakeLists.txt +++ b/clang/unittests/Tooling/CMakeLists.txt @@ -50,6 +50,7 @@ RecursiveASTVisitorTests/NestedNameSpecifiers.cpp RecursiveASTVisitorTests/ParenExpr.cpp RecursiveASTVisitorTests/TemplateArgumentLocTraverser.cpp + RecursiveASTVisitorTests/Templates.cpp RecursiveASTVisitorTests/TraversalScope.cpp RecursiveASTVisitorTestDeclVisitor.cpp RecursiveASTVisitorTestPostOrderVisitor.cpp diff --git a/clang/unittests/Tooling/RecursiveASTVisitorTests/Templates.cpp b/clang/unittests/Tooling/RecursiveASTVisitorTests/Templates.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Tooling/RecursiveASTVisitorTests/Templates.cpp @@ -0,0 +1,409 @@ +//===- Templates.cpp ------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This tests exactly which template-related declarations are traversed by +// RecursiveASTVisitor in the default mode (written code only) and +// shouldVisitTemplateInstantiations() mode. +// +// Because RecursiveASTVisitor should also be able to traverse arbitrary +// subtrees, the nodes representing explicit instantiations should be partially +// traversed when passed to TraverseDecl(). This is also tested. +// (The historical behavior for functions with shouldVisitTI() off was: +// explicit function instantiations were not visited at all, but if explicitly +// passed to TraverseDecl() then the whole body was traversed). +// +//===----------------------------------------------------------------------===// + +#include "TestVisitor.h" +#include "clang/Basic/Specifiers.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace clang; + +namespace { + +// This RecursiveASTVisitor logs the traversal of template-related declarations, +// as well as a few leaf nodes (so we can tell whether bodies are traversed). +class TemplateVisitor : public TestVisitor { + using Base = TestVisitor; + +public: + bool Instantiations = false; + std::function ChooseTraversalRoot = + [](ASTContext &Ctx) { return Ctx.getTranslationUnitDecl(); }; + + bool shouldVisitTemplateInstantiations() { return Instantiations; } + bool shouldVisitImplicitCode() { return false; } + + bool TraverseAST(ASTContext &Ctx) { + return TraverseDecl(ChooseTraversalRoot(Ctx)); + } + +#define LOG_VISIT(NodeTy) \ + bool Visit##NodeTy(NodeTy *N) { \ + addLog("Visit" #NodeTy); \ + addNodeDetail(N); \ + return Base::Visit##NodeTy(N); \ + } +#define LOG_TRAVERSE(NodeTy) \ + LOG_VISIT(NodeTy) \ + bool Traverse##NodeTy(NodeTy *N) { \ + addLog("Traverse" #NodeTy); \ + ++Indent; \ + bool Ret = Base::Traverse##NodeTy(N); \ + --Indent; \ + return Ret; \ + } + LOG_TRAVERSE(FunctionTemplateDecl) + LOG_TRAVERSE(ClassTemplateDecl) + LOG_TRAVERSE(VarTemplateDecl) + LOG_TRAVERSE(FunctionDecl) + LOG_TRAVERSE(CXXRecordDecl) + LOG_TRAVERSE(VarDecl) + LOG_TRAVERSE(ClassTemplateSpecializationDecl) + LOG_TRAVERSE(ClassTemplatePartialSpecializationDecl) + LOG_TRAVERSE(VarTemplateSpecializationDecl) + LOG_TRAVERSE(VarTemplatePartialSpecializationDecl) + LOG_VISIT(CompoundStmt) + LOG_VISIT(FieldDecl) + LOG_VISIT(BinaryOperator) +#undef LOG_TRAVERSE +#undef LOG_VISIT + + std::string takeLog() { + std::string Result = llvm::join(Log, "\n"); + Log.clear(); + return Result; + } + +private: + template void addNodeDetail(T) {} + void addNodeDetail(FunctionDecl *D) { + addDetail(D->getNameAsString()); + if (D->getDescribedFunctionTemplate()) + addDetail("Generic"); + addDetail(D->getTemplateSpecializationKind()); + } + void addNodeDetail(CXXRecordDecl *D) { + addDetail(D->getNameAsString()); + if (D->getDescribedClassTemplate()) + addDetail("Generic"); + addDetail(D->getTemplateSpecializationKind()); + } + void addNodeDetail(VarDecl *D) { + addDetail(D->getNameAsString()); + if (D->getDescribedVarTemplate()) + addDetail("Generic"); + addDetail(D->getTemplateSpecializationKind()); + } + + void addDetail(TemplateSpecializationKind TSK) { + switch (TSK) { + case TSK_Undeclared: + break; + case TSK_ImplicitInstantiation: + addDetail("ImplicitInstantiation"); + break; + case TSK_ExplicitSpecialization: + addDetail("ExplicitSpecialization"); + break; + case TSK_ExplicitInstantiationDeclaration: + addDetail("ExplicitInstantiationDeclaration"); + break; + case TSK_ExplicitInstantiationDefinition: + addDetail("ExplicitInstantiationDefinition"); + break; + } + } + + void addLog(llvm::StringRef Message) { + Log.emplace_back(2 * Indent, ' '); + Log.back().append(Message.begin(), Message.end()); + } + void addDetail(llvm::StringRef Detail) { + Log.back().push_back(' '); + Log.back().append(Detail.begin(), Detail.end()); + } + unsigned Indent = 0; + std::vector Log; +}; + +void checkLog(llvm::StringRef Description, TemplateVisitor &Visitor, + llvm::StringRef Code, llvm::StringRef Expected) { + SCOPED_TRACE(Code); + EXPECT_TRUE(Visitor.runOver(Code, TemplateVisitor::Lang_CXX14)); + EXPECT_EQ(Expected.trim(), Visitor.takeLog()) << Description; +} + +TEST(RecursiveASTVisitor, VisitsFunctionTemplates) { + TemplateVisitor Visitor; + const llvm::StringRef Code = R"cpp( + // Primary template. Should be traversed. + template int foo() { return 0; } + // Explicit specialization. Should be traversed. + template <> int foo<1>() { return 1; } + // Implicit instantiation. Should be traversed if instantiations are on. + int x = foo<2>(); + // Explicit instantiation declaration. Head should be traversed (no body). + extern template int foo<3>(); + // Explicit instantiation. Head traversed, body only with instantiations on. + template int foo<4>(); + )cpp"; + + // FIXME: explicit instantiation decl/definition are missing. + checkLog("Default mode, without instantiations", Visitor, Code, R"( +TraverseFunctionTemplateDecl + VisitFunctionTemplateDecl + TraverseFunctionDecl + VisitFunctionDecl foo Generic + VisitCompoundStmt +TraverseFunctionDecl + VisitFunctionDecl foo ExplicitSpecialization + VisitCompoundStmt +TraverseVarDecl + VisitVarDecl x +)"); + + Visitor.Instantiations = true; + checkLog("With instantiations", Visitor, Code, R"( +TraverseFunctionTemplateDecl + VisitFunctionTemplateDecl + TraverseFunctionDecl + VisitFunctionDecl foo Generic + VisitCompoundStmt + TraverseFunctionDecl + VisitFunctionDecl foo ImplicitInstantiation + VisitCompoundStmt + TraverseFunctionDecl + VisitFunctionDecl foo ExplicitInstantiationDeclaration + TraverseFunctionDecl + VisitFunctionDecl foo ExplicitInstantiationDefinition + VisitCompoundStmt +TraverseFunctionDecl + VisitFunctionDecl foo ExplicitSpecialization + VisitCompoundStmt +TraverseVarDecl + VisitVarDecl x +)"); + + Visitor.Instantiations = false; + Visitor.ChooseTraversalRoot = [](ASTContext &Ctx) -> Decl * { + for (auto *D : Ctx.getTranslationUnitDecl()->decls()) { + if (auto *FTD = llvm::dyn_cast(D)) { + for (auto *Spec : FTD->specializations()) + if (Spec->getTemplateSpecializationKind() == + clang::TSK_ExplicitInstantiationDefinition) + return Spec; + } + } + ADD_FAILURE() << "Could not find an explicit instantiation!"; + return Ctx.getTranslationUnitDecl(); + }; + // FIXME: we should not visit the body, as instantiation traversals are off. + checkLog("Explicitly traversing an explicit instantiation", Visitor, Code, R"( +TraverseFunctionDecl + VisitFunctionDecl foo ExplicitInstantiationDefinition + VisitCompoundStmt +)"); +} + +TEST(RecursiveASTVisitor, VisitsClassTemplates) { + TemplateVisitor Visitor; + const llvm::StringRef Code = R"cpp( + // Primary template. Should be traversed. + template class foo { int a; }; + // Explicit specialization. Should be traversed. + template <> class foo { int b; }; + // Partial specialization. Should be traversed. + template class foo { int c; }; + // Implicit instantiation. Should be traversed if instantiations are on. + foo x; + // Explicit instantiation declaration. Head should be traversed (no body). + extern template class foo; + // Explicit instantiation. Head traversed, body only with instantiations on. + template class foo; + )cpp"; + + checkLog("Default mode, without instantiations", Visitor, Code, R"( +TraverseClassTemplateDecl + VisitClassTemplateDecl + TraverseCXXRecordDecl + VisitCXXRecordDecl foo Generic + VisitFieldDecl +TraverseClassTemplateSpecializationDecl + VisitCXXRecordDecl foo ExplicitSpecialization + VisitClassTemplateSpecializationDecl + VisitFieldDecl +TraverseClassTemplatePartialSpecializationDecl + VisitCXXRecordDecl foo ExplicitSpecialization + VisitClassTemplateSpecializationDecl + VisitClassTemplatePartialSpecializationDecl + VisitFieldDecl +TraverseVarDecl + VisitVarDecl x +TraverseClassTemplateSpecializationDecl + VisitCXXRecordDecl foo ExplicitInstantiationDeclaration + VisitClassTemplateSpecializationDecl +TraverseClassTemplateSpecializationDecl + VisitCXXRecordDecl foo ExplicitInstantiationDefinition + VisitClassTemplateSpecializationDecl +)"); + + Visitor.Instantiations = true; + checkLog("With instantiations", Visitor, Code, R"( +TraverseClassTemplateDecl + VisitClassTemplateDecl + TraverseCXXRecordDecl + VisitCXXRecordDecl foo Generic + VisitFieldDecl + TraverseClassTemplateSpecializationDecl + VisitCXXRecordDecl foo ImplicitInstantiation + VisitClassTemplateSpecializationDecl + VisitFieldDecl +TraverseClassTemplateSpecializationDecl + VisitCXXRecordDecl foo ExplicitSpecialization + VisitClassTemplateSpecializationDecl + VisitFieldDecl +TraverseClassTemplatePartialSpecializationDecl + VisitCXXRecordDecl foo ExplicitSpecialization + VisitClassTemplateSpecializationDecl + VisitClassTemplatePartialSpecializationDecl + VisitFieldDecl +TraverseVarDecl + VisitVarDecl x +TraverseClassTemplateSpecializationDecl + VisitCXXRecordDecl foo ExplicitInstantiationDeclaration + VisitClassTemplateSpecializationDecl + VisitFieldDecl +TraverseClassTemplateSpecializationDecl + VisitCXXRecordDecl foo ExplicitInstantiationDefinition + VisitClassTemplateSpecializationDecl + VisitFieldDecl +)"); + + Visitor.Instantiations = false; + Visitor.ChooseTraversalRoot = [](ASTContext &Ctx) -> Decl * { + for (auto *D : Ctx.getTranslationUnitDecl()->decls()) { + if (auto *Spec = llvm::dyn_cast(D)) { + if (Spec->getTemplateSpecializationKind() == + clang::TSK_ExplicitInstantiationDefinition) + return Spec; + } + } + ADD_FAILURE() << "Could not find an explicit instantiation!"; + return Ctx.getTranslationUnitDecl(); + }; + checkLog("Explicitly traversing an explicit instantiation", Visitor, Code, R"( +TraverseClassTemplateSpecializationDecl + VisitCXXRecordDecl foo ExplicitInstantiationDefinition + VisitClassTemplateSpecializationDecl +)"); +} + +TEST(RecursiveASTVisitor, VisitsVarTemplates) { + TemplateVisitor Visitor; + const llvm::StringRef Code = R"cpp( + // Primary template. Should be traversed. + template float foo = 1 + 1; + // Explicit specialization. Should be traversed. + template <> float foo = 2 + 2; + // Partial specialization. Should be traversed. + template float foo = 3 + 3; + // Implicit instantiation. Should be traversed if instantiations are on. + float x = foo; + // Explicit instantiation declaration. Head should be traversed (no body). + extern template float foo; + // Explicit instantiation. Head traversed, body only with instantiations on. + template float foo; + )cpp"; + + // FIXME: explicit specialization's initializer should be traversed. + // FIXME: implicit instantiation should not be traversed. + checkLog("Default mode, without instantiations", Visitor, Code, R"( +TraverseVarTemplateDecl + VisitVarTemplateDecl + TraverseVarDecl + VisitVarDecl foo Generic + VisitBinaryOperator +TraverseVarTemplateSpecializationDecl + VisitVarDecl foo ExplicitSpecialization + VisitVarTemplateSpecializationDecl +TraverseVarTemplatePartialSpecializationDecl + VisitVarDecl foo ExplicitSpecialization + VisitVarTemplateSpecializationDecl + VisitVarTemplatePartialSpecializationDecl + VisitBinaryOperator +TraverseVarDecl + VisitVarDecl x +TraverseVarTemplateSpecializationDecl + VisitVarDecl foo ImplicitInstantiation + VisitVarTemplateSpecializationDecl +TraverseVarTemplateSpecializationDecl + VisitVarDecl foo ExplicitInstantiationDeclaration + VisitVarTemplateSpecializationDecl +TraverseVarTemplateSpecializationDecl + VisitVarDecl foo ExplicitInstantiationDefinition + VisitVarTemplateSpecializationDecl +)"); + + Visitor.Instantiations = true; + // FIXME: implicit instantiation is traversed twice. + // FIXME: implicit instantiation's initializer should be traversed. + // FIXME: explicit specialization's initializer should be traversed. + // FIXME: explicit instantiation's initializer should be traversed. + checkLog("With instantiations", Visitor, Code, R"( +TraverseVarTemplateDecl + VisitVarTemplateDecl + TraverseVarDecl + VisitVarDecl foo Generic + VisitBinaryOperator + TraverseVarTemplateSpecializationDecl + VisitVarDecl foo ImplicitInstantiation + VisitVarTemplateSpecializationDecl +TraverseVarTemplateSpecializationDecl + VisitVarDecl foo ExplicitSpecialization + VisitVarTemplateSpecializationDecl +TraverseVarTemplatePartialSpecializationDecl + VisitVarDecl foo ExplicitSpecialization + VisitVarTemplateSpecializationDecl + VisitVarTemplatePartialSpecializationDecl + VisitBinaryOperator +TraverseVarDecl + VisitVarDecl x +TraverseVarTemplateSpecializationDecl + VisitVarDecl foo ImplicitInstantiation + VisitVarTemplateSpecializationDecl +TraverseVarTemplateSpecializationDecl + VisitVarDecl foo ExplicitInstantiationDeclaration + VisitVarTemplateSpecializationDecl +TraverseVarTemplateSpecializationDecl + VisitVarDecl foo ExplicitInstantiationDefinition + VisitVarTemplateSpecializationDecl +)"); + + Visitor.Instantiations = false; + Visitor.ChooseTraversalRoot = [](ASTContext &Ctx) -> Decl * { + for (auto *D : Ctx.getTranslationUnitDecl()->decls()) { + if (auto *Spec = llvm::dyn_cast(D)) { + if (Spec->getTemplateSpecializationKind() == + clang::TSK_ExplicitInstantiationDefinition) + return Spec; + } + } + ADD_FAILURE() << "Could not find an explicit instantiation!"; + return Ctx.getTranslationUnitDecl(); + }; + checkLog("Explicitly traversing an explicit instantiation", Visitor, Code, R"( +TraverseVarTemplateSpecializationDecl + VisitVarDecl foo ExplicitInstantiationDefinition + VisitVarTemplateSpecializationDecl +)"); +} + +} // end anonymous namespace diff --git a/clang/unittests/Tooling/TestVisitor.h b/clang/unittests/Tooling/TestVisitor.h --- a/clang/unittests/Tooling/TestVisitor.h +++ b/clang/unittests/Tooling/TestVisitor.h @@ -95,7 +95,7 @@ void HandleTranslationUnit(clang::ASTContext &Context) override { Visitor->Context = &Context; - Visitor->TraverseDecl(Context.getTranslationUnitDecl()); + Visitor->getDerived().TraverseAST(Context); } private: