Skip to content

Commit

Permalink
Unify and simplify the behavior of the hasDeclaration matcher.
Browse files Browse the repository at this point in the history
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

llvm-svn: 309809
  • Loading branch information
r4nt committed Aug 2, 2017
1 parent f23bb4a commit 696e505
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 68 deletions.
27 changes: 27 additions & 0 deletions clang/docs/LibASTMatchersReference.html
Expand Up @@ -1729,6 +1729,21 @@ <h2 id="decl-matchers">Node Matchers</h2>
</pre></td></tr>


<tr><td>Matcher&lt;<a href="http://clang.llvm.org/doxygen/classclang_1_1Type.html">Type</a>&gt;</td><td class="name" onclick="toggle('tagType0')"><a name="tagType0Anchor">tagType</a></td><td>Matcher&lt;<a href="http://clang.llvm.org/doxygen/classclang_1_1TagType.html">TagType</a>&gt;...</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&lt;<a href="http://clang.llvm.org/doxygen/classclang_1_1Type.html">Type</a>&gt;</td><td class="name" onclick="toggle('templateSpecializationType0')"><a name="templateSpecializationType0Anchor">templateSpecializationType</a></td><td>Matcher&lt;<a href="http://clang.llvm.org/doxygen/classclang_1_1TemplateSpecializationType.html">TemplateSpecializationType</a>&gt;...</td></tr>
<tr><td colspan="4" class="doc" id="templateSpecializationType0"><pre>Matches template specialization types.

Expand Down Expand Up @@ -4546,6 +4561,18 @@ <h2 id="traversal-matchers">AST Traversal Matchers</h2>
</pre></td></tr>


<tr><td>Matcher&lt;<a href="http://clang.llvm.org/doxygen/classclang_1_1ClassTemplateSpecializationDecl.html">ClassTemplateSpecializationDecl</a>&gt;</td><td class="name" onclick="toggle('hasSpecializedTemplate0')"><a name="hasSpecializedTemplate0Anchor">hasSpecializedTemplate</a></td><td>Matcher&lt;<a href="http://clang.llvm.org/doxygen/classclang_1_1ClassTemplateDecl.html">ClassTemplateDecl</a>&gt; InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="hasSpecializedTemplate0"><pre>Matches the specialized template of a specialization declaration.

Given
tempalate&lt;typename T&gt; class A {};
typedef A&lt;int&gt; B;
classTemplateSpecializationDecl(hasSpecializedTemplate(classTemplateDecl()))
matches 'B' with classTemplateDecl() matching the class template
declaration of 'A'.
</pre></td></tr>


<tr><td>Matcher&lt;<a href="http://clang.llvm.org/doxygen/classclang_1_1ClassTemplateSpecializationDecl.html">ClassTemplateSpecializationDecl</a>&gt;</td><td class="name" onclick="toggle('hasTemplateArgument0')"><a name="hasTemplateArgument0Anchor">hasTemplateArgument</a></td><td>unsigned N, Matcher&lt;<a href="http://clang.llvm.org/doxygen/classclang_1_1TemplateArgument.html">TemplateArgument</a>&gt; 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.
Expand Down
27 changes: 26 additions & 1 deletion clang/docs/ReleaseNotes.rst
Expand Up @@ -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
------------
Expand Down
32 changes: 32 additions & 0 deletions clang/include/clang/ASTMatchers/ASTMatchers.h
Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
///
Expand Down
117 changes: 81 additions & 36 deletions clang/include/clang/ASTMatchers/ASTMatchersInternal.h
Expand Up @@ -728,48 +728,85 @@ class HasDeclarationMatcher : public WrapperMatcherInterface<T> {
}

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
Expand Down Expand Up @@ -811,6 +848,13 @@ class HasDeclarationMatcher : public WrapperMatcherInterface<T> {
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,
Expand Down Expand Up @@ -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
Expand Down
42 changes: 13 additions & 29 deletions clang/unittests/AST/ASTImporterTest.cpp
Expand Up @@ -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) {
Expand Down
35 changes: 33 additions & 2 deletions clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
Expand Up @@ -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"),
Expand All @@ -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) {
Expand All @@ -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;",
Expand Down Expand Up @@ -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;"
Expand All @@ -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

0 comments on commit 696e505

Please sign in to comment.