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

llvm-svn: 309809
This commit is contained in:
Manuel Klimek 2017-08-02 13:04:44 +00:00
parent f23bb4a8ba
commit 696e505278
6 changed files with 212 additions and 68 deletions

View File

@ -1729,6 +1729,21 @@ substTemplateTypeParmType() matches the type of 't' but not '1'
</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.
@ -4546,6 +4561,18 @@ functionDecl(hasAnyTemplateArgument(refersToType(asString("int"))))
</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.

View File

@ -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
------------

View File

@ -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.
///

View File

@ -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

View File

@ -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) {

View File

@ -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