From 81937d827dc41f18c52b48e4da81467c493de0a0 Mon Sep 17 00:00:00 2001 From: Manuel Klimek <klimek@google.com> Date: Wed, 2 Aug 2017 13:04:44 +0000 Subject: [PATCH] Unify and simplify the behavior of the hasDeclaration matcher. Originally, we weren't able to match on Type nodes themselves (only QualType), so the hasDeclaration matcher was initially written to give what we thought are reasonable results for QualType matches. When we chagned the matchers to allow matching on Type nodes, it turned out that the hasDeclaration matcher was by chance written templated enough to now allow hasDeclaration to also match on (some) Type nodes. This patch change the hasDeclaration matcher to: a) work the same on Type and QualType nodes, b) be completely explicit about what nodes we can match instead of just allowing anything with a getDecl() to match, c) explicitly control desugaring only one level in very specific instances. d) adds hasSpecializedTemplate and tagType matchers to allow migrating existing use cases that now need more explicit matchers Note: This patch breaks clang-tools-extra. The corresponding patch there is approved and will land in a subsequent patch. Differential Revision: https://reviews.llvm.org/D27104 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@309809 91177308-0d34-0410-b5e6-96231b3b80d8 --- docs/LibASTMatchersReference.html | 27 ++++ docs/ReleaseNotes.rst | 27 +++- include/clang/ASTMatchers/ASTMatchers.h | 32 +++++ .../clang/ASTMatchers/ASTMatchersInternal.h | 117 ++++++++++++------ unittests/AST/ASTImporterTest.cpp | 42 ++----- .../ASTMatchers/ASTMatchersTraversalTest.cpp | 35 +++++- 6 files changed, 212 insertions(+), 68 deletions(-) diff --git a/docs/LibASTMatchersReference.html b/docs/LibASTMatchersReference.html index cb5020af49c..ac90c1e629e 100644 --- a/docs/LibASTMatchersReference.html +++ b/docs/LibASTMatchersReference.html @@ -1729,6 +1729,21 @@ substTemplateTypeParmType() matches the type of 't' but not '1' </pre></td></tr> +<tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1Type.html">Type</a>></td><td class="name" onclick="toggle('tagType0')"><a name="tagType0Anchor">tagType</a></td><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1TagType.html">TagType</a>>...</td></tr> +<tr><td colspan="4" class="doc" id="tagType0"><pre>Matches tag types (record and enum types). + +Given + enum E {}; + class C {}; + + E e; + C c; + +tagType() matches the type of the variable declarations of both e +and c. +</pre></td></tr> + + <tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1Type.html">Type</a>></td><td class="name" onclick="toggle('templateSpecializationType0')"><a name="templateSpecializationType0Anchor">templateSpecializationType</a></td><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1TemplateSpecializationType.html">TemplateSpecializationType</a>>...</td></tr> <tr><td colspan="4" class="doc" id="templateSpecializationType0"><pre>Matches template specialization types. @@ -4546,6 +4561,18 @@ functionDecl(hasAnyTemplateArgument(refersToType(asString("int")))) </pre></td></tr> +<tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1ClassTemplateSpecializationDecl.html">ClassTemplateSpecializationDecl</a>></td><td class="name" onclick="toggle('hasSpecializedTemplate0')"><a name="hasSpecializedTemplate0Anchor">hasSpecializedTemplate</a></td><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1ClassTemplateDecl.html">ClassTemplateDecl</a>> InnerMatcher</td></tr> +<tr><td colspan="4" class="doc" id="hasSpecializedTemplate0"><pre>Matches the specialized template of a specialization declaration. + +Given + tempalate<typename T> class A {}; + typedef A<int> B; +classTemplateSpecializationDecl(hasSpecializedTemplate(classTemplateDecl())) + matches 'B' with classTemplateDecl() matching the class template + declaration of 'A'. +</pre></td></tr> + + <tr><td>Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1ClassTemplateSpecializationDecl.html">ClassTemplateSpecializationDecl</a>></td><td class="name" onclick="toggle('hasTemplateArgument0')"><a name="hasTemplateArgument0Anchor">hasTemplateArgument</a></td><td>unsigned N, Matcher<<a href="http://clang.llvm.org/doxygen/classclang_1_1TemplateArgument.html">TemplateArgument</a>> InnerMatcher</td></tr> <tr><td colspan="4" class="doc" id="hasTemplateArgument0"><pre>Matches classTemplateSpecializations, templateSpecializationType and functionDecl where the n'th TemplateArgument matches the given InnerMatcher. diff --git a/docs/ReleaseNotes.rst b/docs/ReleaseNotes.rst index 97b53b13a83..49ed87d9a9a 100644 --- a/docs/ReleaseNotes.rst +++ b/docs/ReleaseNotes.rst @@ -151,8 +151,33 @@ this section should help get you past the largest hurdles of upgrading. AST Matchers ------------ -... +The hasDeclaration matcher now works the same for Type and QualType and only +ever looks through one level of sugaring in a limited number of cases. + +There are two main patterns affected by this: + +- qualType(hasDeclaration(recordDecl(...))): previously, we would look through + sugar like TypedefType to get at the underlying recordDecl; now, we need + to explicitly remove the sugaring: + qualType(hasUnqualifiedDesugaredType(hasDeclaration(recordDecl(...)))) + +- hasType(recordDecl(...)): hasType internally uses hasDeclaration; previously, + this matcher used to match for example TypedefTypes of the RecordType, but + after the change they don't; to fix, use: + +:: + hasType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(recordDecl(...))))) + +- templateSpecializationType(hasDeclaration(classTemplateDecl(...))): + previously, we would directly match the underlying ClassTemplateDecl; + now, we can explicitly match the ClassTemplateSpecializationDecl, but that + requires to explicitly get the ClassTemplateDecl: +:: + templateSpecializationType(hasDeclaration( + classTemplateSpecializationDecl( + hasSpecializedTemplate(classTemplateDecl(...))))) clang-format ------------ diff --git a/include/clang/ASTMatchers/ASTMatchers.h b/include/clang/ASTMatchers/ASTMatchers.h index a4fc6faa663..b2e444322c8 100644 --- a/include/clang/ASTMatchers/ASTMatchers.h +++ b/include/clang/ASTMatchers/ASTMatchers.h @@ -582,6 +582,23 @@ AST_MATCHER_P(FieldDecl, hasInClassInitializer, internal::Matcher<Expr>, InnerMatcher.matches(*Initializer, Finder, Builder)); } +/// \brief Matches the specialized template of a specialization declaration. +/// +/// Given +/// \code +/// tempalate<typename T> class A {}; +/// typedef A<int> B; +/// \endcode +/// classTemplateSpecializationDecl(hasSpecializedTemplate(classTemplateDecl())) +/// matches 'B' with classTemplateDecl() matching the class template +/// declaration of 'A'. +AST_MATCHER_P(ClassTemplateSpecializationDecl, hasSpecializedTemplate, + internal::Matcher<ClassTemplateDecl>, InnerMatcher) { + const ClassTemplateDecl* Decl = Node.getSpecializedTemplate(); + return (Decl != nullptr && + InnerMatcher.matches(*Decl, Finder, Builder)); +} + /// \brief Matches a declaration that has been implicitly added /// by the compiler (eg. implicit default/copy constructors). AST_MATCHER(Decl, isImplicit) { @@ -5083,6 +5100,21 @@ AST_TYPE_MATCHER(UnaryTransformType, unaryTransformType); /// and \c s. AST_TYPE_MATCHER(RecordType, recordType); +/// \brief Matches tag types (record and enum types). +/// +/// Given +/// \code +/// enum E {}; +/// class C {}; +/// +/// E e; +/// C c; +/// \endcode +/// +/// \c tagType() matches the type of the variable declarations of both \c e +/// and \c c. +AST_TYPE_MATCHER(TagType, tagType); + /// \brief Matches types specified with an elaborated type keyword or with a /// qualified name. /// diff --git a/include/clang/ASTMatchers/ASTMatchersInternal.h b/include/clang/ASTMatchers/ASTMatchersInternal.h index bc75e807ced..2a85ac6c99a 100644 --- a/include/clang/ASTMatchers/ASTMatchersInternal.h +++ b/include/clang/ASTMatchers/ASTMatchersInternal.h @@ -728,48 +728,85 @@ public: } private: - /// \brief If getDecl exists as a member of U, returns whether the inner - /// matcher matches Node.getDecl(). - template <typename U> - bool matchesSpecialized( - const U &Node, ASTMatchFinder *Finder, BoundNodesTreeBuilder *Builder, - typename std::enable_if<has_getDecl<U>::value, int>::type = 0) const { - return matchesDecl(Node.getDecl(), Finder, Builder); - } - - /// \brief Extracts the TagDecl of a QualType and returns whether the inner - /// matcher matches on it. + /// \brief Forwards to matching on the underlying type of the QualType. bool matchesSpecialized(const QualType &Node, ASTMatchFinder *Finder, BoundNodesTreeBuilder *Builder) const { if (Node.isNull()) return false; - if (auto *TD = Node->getAsTagDecl()) - return matchesDecl(TD, Finder, Builder); - else if (auto *TT = Node->getAs<TypedefType>()) - return matchesDecl(TT->getDecl(), Finder, Builder); - // Do not use getAs<TemplateTypeParmType> instead of the direct dyn_cast. - // Calling getAs will return the canonical type, but that type does not - // store a TemplateTypeParmDecl. We *need* the uncanonical type, if it is - // available, and using dyn_cast ensures that. - else if (auto *TTP = dyn_cast<TemplateTypeParmType>(Node.getTypePtr())) - return matchesDecl(TTP->getDecl(), Finder, Builder); - else if (auto *OCIT = Node->getAs<ObjCInterfaceType>()) - return matchesDecl(OCIT->getDecl(), Finder, Builder); - else if (auto *UUT = Node->getAs<UnresolvedUsingType>()) - return matchesDecl(UUT->getDecl(), Finder, Builder); - else if (auto *ICNT = Node->getAs<InjectedClassNameType>()) - return matchesDecl(ICNT->getDecl(), Finder, Builder); + return matchesSpecialized(*Node, Finder, Builder); + } + + /// \brief Finds the best declaration for a type and returns whether the inner + /// matcher matches on it. + bool matchesSpecialized(const Type &Node, ASTMatchFinder *Finder, + BoundNodesTreeBuilder *Builder) const { + // First, for any types that have a declaration, extract the declaration and + // match on it. + if (const auto *S = dyn_cast<TagType>(&Node)) { + return matchesDecl(S->getDecl(), Finder, Builder); + } + if (const auto *S = dyn_cast<InjectedClassNameType>(&Node)) { + return matchesDecl(S->getDecl(), Finder, Builder); + } + if (const auto *S = dyn_cast<TemplateTypeParmType>(&Node)) { + return matchesDecl(S->getDecl(), Finder, Builder); + } + if (const auto *S = dyn_cast<TypedefType>(&Node)) { + return matchesDecl(S->getDecl(), Finder, Builder); + } + if (const auto *S = dyn_cast<UnresolvedUsingType>(&Node)) { + return matchesDecl(S->getDecl(), Finder, Builder); + } + if (const auto *S = dyn_cast<ObjCObjectType>(&Node)) { + return matchesDecl(S->getInterface(), Finder, Builder); + } + + // A SubstTemplateTypeParmType exists solely to mark a type substitution + // on the instantiated template. As users usually want to match the + // template parameter on the uninitialized template, we can always desugar + // one level without loss of expressivness. + // For example, given: + // template<typename T> struct X { T t; } class A {}; X<A> a; + // The following matcher will match, which otherwise would not: + // fieldDecl(hasType(pointerType())). + if (const auto *S = dyn_cast<SubstTemplateTypeParmType>(&Node)) { + return matchesSpecialized(S->getReplacementType(), Finder, Builder); + } + + // For template specialization types, we want to match the template + // declaration, as long as the type is still dependent, and otherwise the + // declaration of the instantiated tag type. + if (const auto *S = dyn_cast<TemplateSpecializationType>(&Node)) { + if (!S->isTypeAlias() && S->isSugared()) { + // If the template is non-dependent, we want to match the instantiated + // tag type. + // For example, given: + // template<typename T> struct X {}; X<int> a; + // The following matcher will match, which otherwise would not: + // templateSpecializationType(hasDeclaration(cxxRecordDecl())). + return matchesSpecialized(*S->desugar(), Finder, Builder); + } + // If the template is dependent or an alias, match the template + // declaration. + return matchesDecl(S->getTemplateName().getAsTemplateDecl(), Finder, + Builder); + } + + // FIXME: We desugar elaborated types. This makes the assumption that users + // do never want to match on whether a type is elaborated - there are + // arguments for both sides; for now, continue desugaring. + if (const auto *S = dyn_cast<ElaboratedType>(&Node)) { + return matchesSpecialized(S->desugar(), Finder, Builder); + } return false; } - /// \brief Gets the TemplateDecl from a TemplateSpecializationType - /// and returns whether the inner matches on it. - bool matchesSpecialized(const TemplateSpecializationType &Node, - ASTMatchFinder *Finder, + /// \brief Extracts the Decl the DeclRefExpr references and returns whether + /// the inner matcher matches on it. + bool matchesSpecialized(const DeclRefExpr &Node, ASTMatchFinder *Finder, BoundNodesTreeBuilder *Builder) const { - return matchesDecl(Node.getTemplateName().getAsTemplateDecl(), - Finder, Builder); + return matchesDecl(Node.getDecl(), Finder, Builder); } /// \brief Extracts the Decl of the callee of a CallExpr and returns whether @@ -811,6 +848,13 @@ private: return matchesDecl(Node.getLabel(), Finder, Builder); } + /// \brief Extracts the declaration of a LabelStmt and returns whether the + /// inner matcher matches on it. + bool matchesSpecialized(const LabelStmt &Node, ASTMatchFinder *Finder, + BoundNodesTreeBuilder *Builder) const { + return matchesDecl(Node.getDecl(), Finder, Builder); + } + /// \brief Returns whether the inner matcher \c Node. Returns false if \c Node /// is \c NULL. bool matchesDecl(const Decl *Node, ASTMatchFinder *Finder, @@ -1016,9 +1060,10 @@ typedef TypeList<Decl, Stmt, NestedNameSpecifier, NestedNameSpecifierLoc, /// \brief All types that are supported by HasDeclarationMatcher above. typedef TypeList<CallExpr, CXXConstructExpr, CXXNewExpr, DeclRefExpr, EnumType, - InjectedClassNameType, LabelStmt, AddrLabelExpr, MemberExpr, - QualType, RecordType, TagType, TemplateSpecializationType, - TemplateTypeParmType, TypedefType, UnresolvedUsingType> + ElaboratedType, InjectedClassNameType, LabelStmt, + AddrLabelExpr, MemberExpr, QualType, RecordType, TagType, + TemplateSpecializationType, TemplateTypeParmType, TypedefType, + UnresolvedUsingType> HasDeclarationSupportedTypes; /// \brief Converts a \c Matcher<T> to a matcher of desired type \c To by diff --git a/unittests/AST/ASTImporterTest.cpp b/unittests/AST/ASTImporterTest.cpp index 57b41f12b0b..485cf252c15 100644 --- a/unittests/AST/ASTImporterTest.cpp +++ b/unittests/AST/ASTImporterTest.cpp @@ -252,35 +252,19 @@ AST_MATCHER_P(TemplateDecl, hasTemplateDecl, TEST(ImportExpr, ImportParenListExpr) { MatchVerifier<Decl> Verifier; - EXPECT_TRUE( - testImport( - "template<typename T> class dummy { void f() { dummy X(*this); } };" - "typedef dummy<int> declToImport;" - "template class dummy<int>;", - Lang_CXX, "", Lang_CXX, Verifier, - typedefDecl( - hasType( - templateSpecializationType( - hasDeclaration( - classTemplateDecl( - hasTemplateDecl( - cxxRecordDecl( - hasMethod( - allOf( - hasName("f"), - hasBody( - compoundStmt( - has( - declStmt( - hasSingleDecl( - varDecl( - hasInitializer( - parenListExpr( - has( - unaryOperator( - hasOperatorName("*"), - hasUnaryOperand(cxxThisExpr()) - ))))))))))))))))))))); + EXPECT_TRUE(testImport( + "template<typename T> class dummy { void f() { dummy X(*this); } };" + "typedef dummy<int> declToImport;" + "template class dummy<int>;", + Lang_CXX, "", Lang_CXX, Verifier, + typedefDecl(hasType(templateSpecializationType( + hasDeclaration(classTemplateSpecializationDecl(hasSpecializedTemplate( + classTemplateDecl(hasTemplateDecl(cxxRecordDecl(hasMethod(allOf( + hasName("f"), + hasBody(compoundStmt(has(declStmt(hasSingleDecl( + varDecl(hasInitializer(parenListExpr(has(unaryOperator( + hasOperatorName("*"), + hasUnaryOperand(cxxThisExpr())))))))))))))))))))))))); } TEST(ImportExpr, ImportStmtExpr) { diff --git a/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp b/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp index 3e268435ef7..e699e19262e 100644 --- a/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp +++ b/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp @@ -230,6 +230,17 @@ TEST(HasDeclaration, HasGetDeclTraitTest) { "Expected TemplateSpecializationType to *not* have a getDecl."); } +TEST(HasDeclaration, ElaboratedType) { + EXPECT_TRUE(matches( + "namespace n { template <typename T> struct X {}; }" + "void f(n::X<int>);", + parmVarDecl(hasType(qualType(hasDeclaration(cxxRecordDecl())))))); + EXPECT_TRUE(matches( + "namespace n { template <typename T> struct X {}; }" + "void f(n::X<int>);", + parmVarDecl(hasType(elaboratedType(hasDeclaration(cxxRecordDecl())))))); +} + TEST(HasDeclaration, HasDeclarationOfTypeWithDecl) { EXPECT_TRUE(matches("typedef int X; X a;", varDecl(hasName("a"), @@ -242,6 +253,13 @@ TEST(HasDeclaration, HasDeclarationOfTemplateSpecializationType) { EXPECT_TRUE(matches("template <typename T> class A {}; A<int> a;", varDecl(hasType(templateSpecializationType( hasDeclaration(namedDecl(hasName("A")))))))); + EXPECT_TRUE(matches("template <typename T> class A {};" + "template <typename T> class B { A<T> a; };", + fieldDecl(hasType(templateSpecializationType( + hasDeclaration(namedDecl(hasName("A")))))))); + EXPECT_TRUE(matches("template <typename T> class A {}; A<int> a;", + varDecl(hasType(templateSpecializationType( + hasDeclaration(cxxRecordDecl())))))); } TEST(HasDeclaration, HasDeclarationOfCXXNewExpr) { @@ -250,6 +268,12 @@ TEST(HasDeclaration, HasDeclarationOfCXXNewExpr) { cxxNewExpr(hasDeclaration(functionDecl(parameterCountIs(1)))))); } +TEST(HasDeclaration, HasDeclarationOfTypeAlias) { + EXPECT_TRUE(matches("template <typename T> using C = T; C<int> c;", + varDecl(hasType(templateSpecializationType( + hasDeclaration(typeAliasTemplateDecl())))))); +} + TEST(HasUnqualifiedDesugaredType, DesugarsUsing) { EXPECT_TRUE( matches("struct A {}; using B = A; B b;", @@ -2211,8 +2235,7 @@ TEST(Matcher, HasAnyDeclaration) { functionDecl(hasName("bar")))))); } -TEST(SubstTemplateTypeParmType, HasReplacementType) -{ +TEST(SubstTemplateTypeParmType, HasReplacementType) { std::string Fragment = "template<typename T>" "double F(T t);" "int i;" @@ -2228,5 +2251,13 @@ TEST(SubstTemplateTypeParmType, HasReplacementType) substTemplateTypeParmType(hasReplacementType(qualType())))); } +TEST(ClassTemplateSpecializationDecl, HasSpecializedTemplate) { + auto Matcher = classTemplateSpecializationDecl( + hasSpecializedTemplate(classTemplateDecl())); + EXPECT_TRUE( + matches("template<typename T> class A {}; typedef A<int> B;", Matcher)); + EXPECT_TRUE(notMatches("template<typename T> class A {};", Matcher)); +} + } // namespace ast_matchers } // namespace clang -- GitLab