AST Matchers
------------
-- ...
+- The behavior of TK_IgnoreUnlessSpelledInSource with the traverse() matcher
+ has been changed to no longer match on template instantiations or on
+ implicit nodes which are not spelled in the source.
clang-format
------------
TraversalKind GetTraversalKind() const { return Traversal; }
void Visit(const Decl *D) {
+ if (Traversal == TK_IgnoreUnlessSpelledInSource && D->isImplicit())
+ return;
+
getNodeDelegate().AddChild([=] {
getNodeDelegate().Visit(D);
if (!D)
if (isa<DeclStmt>(S) || isa<GenericSelectionExpr>(S))
return;
- if (isa<LambdaExpr>(S) && Traversal == TK_IgnoreUnlessSpelledInSource)
+ if (Traversal == TK_IgnoreUnlessSpelledInSource &&
+ isa<LambdaExpr, CXXForRangeStmt, CallExpr>(S))
return;
for (const Stmt *SubStmt : S->children())
}
void Visit(const CXXCtorInitializer *Init) {
+ if (Traversal == TK_IgnoreUnlessSpelledInSource && !Init->isWritten())
+ return;
getNodeDelegate().AddChild([=] {
getNodeDelegate().Visit(Init);
Visit(Init->getInit());
if (const Expr *TRC = D->getTrailingRequiresClause())
Visit(TRC);
+ if (Traversal == TK_IgnoreUnlessSpelledInSource && D->isDefaulted())
+ return;
+
if (const auto *C = dyn_cast<CXXConstructorDecl>(D))
for (const auto *I : C->inits())
Visit(I);
}
void VisitVarDecl(const VarDecl *D) {
+ if (Traversal == TK_IgnoreUnlessSpelledInSource && D->isCXXForRangeDecl())
+ return;
+
if (D->hasInit())
Visit(D->getInit());
}
Visit(CatchParam);
}
+ void VisitCXXForRangeStmt(const CXXForRangeStmt *Node) {
+ if (Traversal == TK_IgnoreUnlessSpelledInSource) {
+ Visit(Node->getInit());
+ Visit(Node->getLoopVariable());
+ Visit(Node->getRangeInit());
+ Visit(Node->getBody());
+ }
+ }
+
+ void VisitCallExpr(const CallExpr *Node) {
+ for (const auto *Child :
+ make_filter_range(Node->children(), [this](const Stmt *Child) {
+ if (Traversal != TK_IgnoreUnlessSpelledInSource)
+ return false;
+ return !isa<CXXDefaultArgExpr>(Child);
+ })) {
+ Visit(Child);
+ }
+ }
+
void VisitExpressionTemplateArgument(const TemplateArgument &TA) {
Visit(TA.getAsExpr());
}
// matching the descendants.
MatchChildASTVisitor(const DynTypedMatcher *Matcher, ASTMatchFinder *Finder,
BoundNodesTreeBuilder *Builder, int MaxDepth,
- TraversalKind Traversal, ASTMatchFinder::BindKind Bind)
+ TraversalKind Traversal, bool IgnoreImplicitChildren,
+ ASTMatchFinder::BindKind Bind)
: Matcher(Matcher), Finder(Finder), Builder(Builder), CurrentDepth(0),
- MaxDepth(MaxDepth), Traversal(Traversal), Bind(Bind), Matches(false) {}
+ MaxDepth(MaxDepth), Traversal(Traversal),
+ IgnoreImplicitChildren(IgnoreImplicitChildren), Bind(Bind),
+ Matches(false) {}
// Returns true if a match is found in the subtree rooted at the
// given AST node. This is done via a set of mutually recursive
// They are public only to allow CRTP to work. They are *not *part
// of the public API of this class.
bool TraverseDecl(Decl *DeclNode) {
+
+ if (DeclNode && DeclNode->isImplicit() &&
+ Finder->isTraversalIgnoringImplicitNodes())
+ return baseTraverse(*DeclNode);
+
ScopedIncrement ScopedDepth(&CurrentDepth);
return (DeclNode == nullptr) || traverse(*DeclNode);
}
Stmt *StmtToTraverse = getStmtToTraverse(StmtNode);
if (!StmtToTraverse)
return true;
+
+ if (IgnoreImplicitChildren && isa<CXXDefaultArgExpr>(StmtNode))
+ return true;
+
if (!match(*StmtToTraverse))
return false;
return VisitorBase::TraverseStmt(StmtToTraverse, Queue);
}
bool shouldVisitTemplateInstantiations() const { return true; }
- bool shouldVisitImplicitCode() const { return true; }
+ bool shouldVisitImplicitCode() const { return !IgnoreImplicitChildren; }
private:
// Used for updating the depth during traversal.
int CurrentDepth;
const int MaxDepth;
const TraversalKind Traversal;
+ const bool IgnoreImplicitChildren;
const ASTMatchFinder::BindKind Bind;
bool Matches;
};
const DynTypedMatcher &Matcher,
BoundNodesTreeBuilder *Builder, int MaxDepth,
TraversalKind Traversal, BindKind Bind) {
- bool ScopedTraversal = TraversingASTNodeNotSpelledInSource;
+ bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
+ TraversingASTChildrenNotSpelledInSource;
- if (const auto *CTSD = Node.get<ClassTemplateSpecializationDecl>()) {
- int SK = CTSD->getSpecializationKind();
- if (SK == TSK_ExplicitInstantiationDeclaration ||
- SK == TSK_ExplicitInstantiationDefinition)
+ bool IgnoreImplicitChildren = false;
+
+ if (isTraversalIgnoringImplicitNodes()) {
+ IgnoreImplicitChildren = true;
+ if (Node.get<CXXForRangeStmt>())
ScopedTraversal = true;
}
ASTNodeNotSpelledInSourceScope RAII(this, ScopedTraversal);
- MatchChildASTVisitor Visitor(
- &Matcher, this, Builder, MaxDepth, Traversal, Bind);
+ MatchChildASTVisitor Visitor(&Matcher, this, Builder, MaxDepth, Traversal,
+ IgnoreImplicitChildren, Bind);
return Visitor.findMatch(Node);
}
private:
bool TraversingASTNodeNotSpelledInSource = false;
+ bool TraversingASTChildrenNotSpelledInSource = false;
struct ASTNodeNotSpelledInSourceScope {
ASTNodeNotSpelledInSourceScope(MatchASTVisitor *V, bool B)
bool MB;
};
+ struct ASTChildrenNotSpelledInSource {
+ ASTChildrenNotSpelledInSource(MatchASTVisitor *V, bool B)
+ : MV(V), MB(V->TraversingASTChildrenNotSpelledInSource) {
+ V->TraversingASTChildrenNotSpelledInSource = B;
+ }
+ ~ASTChildrenNotSpelledInSource() {
+ MV->TraversingASTChildrenNotSpelledInSource = MB;
+ }
+
+ private:
+ MatchASTVisitor *MV;
+ bool MB;
+ };
+
class TimeBucketRegion {
public:
TimeBucketRegion() : Bucket(nullptr) {}
if (!DeclNode) {
return true;
}
+
+ bool ScopedTraversal =
+ TraversingASTNodeNotSpelledInSource || DeclNode->isImplicit();
+ bool ScopedChildren = TraversingASTChildrenNotSpelledInSource;
+
+ if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(DeclNode)) {
+ auto SK = CTSD->getSpecializationKind();
+ if (SK == TSK_ExplicitInstantiationDeclaration ||
+ SK == TSK_ExplicitInstantiationDefinition)
+ ScopedChildren = true;
+ } else if (const auto *FD = dyn_cast<FunctionDecl>(DeclNode)) {
+ if (FD->isDefaulted())
+ ScopedChildren = true;
+ }
+
+ ASTNodeNotSpelledInSourceScope RAII1(this, ScopedTraversal);
+ ASTChildrenNotSpelledInSource RAII2(this, ScopedChildren);
+
match(*DeclNode);
return RecursiveASTVisitor<MatchASTVisitor>::TraverseDecl(DeclNode);
}
if (!StmtNode) {
return true;
}
+ bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
+ TraversingASTChildrenNotSpelledInSource;
+
+ ASTNodeNotSpelledInSourceScope RAII(this, ScopedTraversal);
match(*StmtNode);
return RecursiveASTVisitor<MatchASTVisitor>::TraverseStmt(StmtNode, Queue);
}
if (!CtorInit)
return true;
+ bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
+ TraversingASTChildrenNotSpelledInSource;
+
+ if (!CtorInit->isWritten())
+ ScopedTraversal = true;
+
+ ASTNodeNotSpelledInSourceScope RAII1(this, ScopedTraversal);
+
match(*CtorInit);
return RecursiveASTVisitor<MatchASTVisitor>::TraverseConstructorInitializer(
}
void Visit(const Stmt *S) {
+ if (!S) {
+ OS << "<<<NULL>>>";
+ return;
+ }
OS << S->getStmtClassName();
if (auto *E = dyn_cast<DeclRefExpr>(S)) {
OS << " '" << E->getDecl()->getDeclName() << "'";
OS << C->getCommentKindName();
}
- void Visit(const CXXCtorInitializer *Init) { OS << "CXXCtorInitializer"; }
+ void Visit(const CXXCtorInitializer *Init) {
+ OS << "CXXCtorInitializer";
+ if (const auto *F = Init->getAnyMember()) {
+ OS << " '" << F->getNameAsString() << "'";
+ } else if (auto const *TSI = Init->getTypeSourceInfo()) {
+ OS << " '" << TSI->getType().getAsString() << "'";
+ }
+ }
void Visit(const Attr *A) {
switch (A->getKind()) {
verifyWithDynNode(Init,
R"cpp(
-CXXCtorInitializer
+CXXCtorInitializer 'm_number'
`-IntegerLiteral
)cpp");
}
}
+TEST(Traverse, IgnoreUnlessSpelledInSourceImplicit) {
+ {
+ auto AST = buildASTFromCode(R"cpp(
+int i = 0;
+)cpp");
+ const auto *TUDecl = AST->getASTContext().getTranslationUnitDecl();
+
+#if _WIN32
+ EXPECT_EQ(dumpASTString(TK_AsIs, TUDecl),
+ R"cpp(
+TranslationUnitDecl
+|-CXXRecordDecl '_GUID'
+| `-TypeVisibilityAttr
+|-TypedefDecl '__int128_t'
+| `-BuiltinType
+|-TypedefDecl '__uint128_t'
+| `-BuiltinType
+|-TypedefDecl '__NSConstantString'
+| `-RecordType
+|-CXXRecordDecl 'type_info'
+| `-TypeVisibilityAttr
+|-TypedefDecl 'size_t'
+| `-BuiltinType
+|-TypedefDecl '__builtin_ms_va_list'
+| `-PointerType
+| `-BuiltinType
+|-TypedefDecl '__builtin_va_list'
+| `-PointerType
+| `-BuiltinType
+`-VarDecl 'i'
+ `-IntegerLiteral
+)cpp");
+#else
+ EXPECT_EQ(dumpASTString(TK_AsIs, TUDecl),
+ R"cpp(
+TranslationUnitDecl
+|-TypedefDecl '__int128_t'
+| `-BuiltinType
+|-TypedefDecl '__uint128_t'
+| `-BuiltinType
+|-TypedefDecl '__NSConstantString'
+| `-RecordType
+|-TypedefDecl '__builtin_ms_va_list'
+| `-PointerType
+| `-BuiltinType
+|-TypedefDecl '__builtin_va_list'
+| `-ConstantArrayType
+| `-RecordType
+`-VarDecl 'i'
+ `-IntegerLiteral
+)cpp");
+#endif
+
+ EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, TUDecl),
+ R"cpp(
+TranslationUnitDecl
+`-VarDecl 'i'
+ `-IntegerLiteral
+)cpp");
+ }
+
+ auto AST2 = buildASTFromCodeWithArgs(R"cpp(
+struct Simple {
+};
+struct Other {
+};
+
+struct Record : Simple, Other {
+ Record() : Simple(), m_i(42) {}
+private:
+ int m_i;
+ int m_i2 = 42;
+ Simple m_s;
+};
+
+struct NonTrivial {
+ NonTrivial() {}
+ NonTrivial(NonTrivial&) {}
+ NonTrivial& operator=(NonTrivial&) { return *this; }
+
+ ~NonTrivial() {}
+};
+
+struct ContainsArray {
+ NonTrivial arr[2];
+ int irr[2];
+ ContainsArray& operator=(ContainsArray &) = default;
+};
+
+void copyIt()
+{
+ ContainsArray ca;
+ ContainsArray ca2;
+ ca2 = ca;
+}
+
+void forLoop()
+{
+ int arr[2];
+ for (auto i : arr)
+ {
+
+ }
+ for (auto& a = arr; auto i : a)
+ {
+
+ }
+}
+
+struct DefaultedAndDeleted {
+ NonTrivial nt;
+ DefaultedAndDeleted() = default;
+ ~DefaultedAndDeleted() = default;
+ DefaultedAndDeleted(DefaultedAndDeleted &) = default;
+ DefaultedAndDeleted& operator=(DefaultedAndDeleted &) = default;
+ DefaultedAndDeleted(DefaultedAndDeleted &&) = delete;
+ DefaultedAndDeleted& operator=(DefaultedAndDeleted &&) = delete;
+};
+
+void copyIt2()
+{
+ DefaultedAndDeleted ca;
+ DefaultedAndDeleted ca2;
+ ca2 = ca;
+}
+
+void hasDefaultArg(int i, int j = 0)
+{
+}
+void callDefaultArg()
+{
+ hasDefaultArg(42);
+}
+)cpp",
+ {"-std=c++20"});
+
+ {
+ auto BN = ast_matchers::match(
+ cxxRecordDecl(hasName("Record"), unless(isImplicit())).bind("rec"),
+ AST2->getASTContext());
+ EXPECT_EQ(BN.size(), 1u);
+
+ EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs<Decl>("rec")),
+ R"cpp(
+CXXRecordDecl 'Record'
+|-CXXRecordDecl 'Record'
+|-CXXConstructorDecl 'Record'
+| |-CXXCtorInitializer 'struct Simple'
+| | `-CXXConstructExpr
+| |-CXXCtorInitializer 'struct Other'
+| | `-CXXConstructExpr
+| |-CXXCtorInitializer 'm_i'
+| | `-IntegerLiteral
+| |-CXXCtorInitializer 'm_i2'
+| | `-CXXDefaultInitExpr
+| |-CXXCtorInitializer 'm_s'
+| | `-CXXConstructExpr
+| `-CompoundStmt
+|-AccessSpecDecl
+|-FieldDecl 'm_i'
+|-FieldDecl 'm_i2'
+| `-IntegerLiteral
+`-FieldDecl 'm_s'
+)cpp");
+
+ EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource,
+ BN[0].getNodeAs<Decl>("rec")),
+ R"cpp(
+CXXRecordDecl 'Record'
+|-CXXConstructorDecl 'Record'
+| |-CXXCtorInitializer 'struct Simple'
+| | `-CXXConstructExpr
+| |-CXXCtorInitializer 'm_i'
+| | `-IntegerLiteral
+| `-CompoundStmt
+|-AccessSpecDecl
+|-FieldDecl 'm_i'
+|-FieldDecl 'm_i2'
+| `-IntegerLiteral
+`-FieldDecl 'm_s'
+)cpp");
+ }
+ {
+ auto BN = ast_matchers::match(
+ cxxRecordDecl(hasName("ContainsArray"), unless(isImplicit()))
+ .bind("rec"),
+ AST2->getASTContext());
+ EXPECT_EQ(BN.size(), 1u);
+
+ EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs<Decl>("rec")),
+ R"cpp(
+CXXRecordDecl 'ContainsArray'
+|-CXXRecordDecl 'ContainsArray'
+|-FieldDecl 'arr'
+|-FieldDecl 'irr'
+|-CXXMethodDecl 'operator='
+| |-ParmVarDecl ''
+| `-CompoundStmt
+| |-ForStmt
+| | |-DeclStmt
+| | | `-VarDecl '__i0'
+| | | `-IntegerLiteral
+| | |-<<<NULL>>>
+| | |-BinaryOperator
+| | | |-ImplicitCastExpr
+| | | | `-DeclRefExpr '__i0'
+| | | `-IntegerLiteral
+| | |-UnaryOperator
+| | | `-DeclRefExpr '__i0'
+| | `-CXXMemberCallExpr
+| | |-MemberExpr
+| | | `-ArraySubscriptExpr
+| | | |-ImplicitCastExpr
+| | | | `-MemberExpr
+| | | | `-CXXThisExpr
+| | | `-ImplicitCastExpr
+| | | `-DeclRefExpr '__i0'
+| | `-ArraySubscriptExpr
+| | |-ImplicitCastExpr
+| | | `-MemberExpr
+| | | `-DeclRefExpr ''
+| | `-ImplicitCastExpr
+| | `-DeclRefExpr '__i0'
+| |-CallExpr
+| | |-ImplicitCastExpr
+| | | `-DeclRefExpr '__builtin_memcpy'
+| | |-ImplicitCastExpr
+| | | `-UnaryOperator
+| | | `-MemberExpr
+| | | `-CXXThisExpr
+| | |-ImplicitCastExpr
+| | | `-UnaryOperator
+| | | `-MemberExpr
+| | | `-DeclRefExpr ''
+| | `-IntegerLiteral
+| `-ReturnStmt
+| `-UnaryOperator
+| `-CXXThisExpr
+|-CXXConstructorDecl 'ContainsArray'
+| `-ParmVarDecl ''
+|-CXXDestructorDecl '~ContainsArray'
+| `-CompoundStmt
+`-CXXConstructorDecl 'ContainsArray'
+ |-CXXCtorInitializer 'arr'
+ | `-CXXConstructExpr
+ `-CompoundStmt
+)cpp");
+
+ EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource,
+ BN[0].getNodeAs<Decl>("rec")),
+ R"cpp(
+CXXRecordDecl 'ContainsArray'
+|-FieldDecl 'arr'
+|-FieldDecl 'irr'
+`-CXXMethodDecl 'operator='
+ `-ParmVarDecl ''
+)cpp");
+ }
+ {
+ auto BN = ast_matchers::match(functionDecl(hasName("forLoop")).bind("func"),
+ AST2->getASTContext());
+ EXPECT_EQ(BN.size(), 1u);
+
+ EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs<Decl>("func")),
+ R"cpp(
+FunctionDecl 'forLoop'
+`-CompoundStmt
+ |-DeclStmt
+ | `-VarDecl 'arr'
+ |-CXXForRangeStmt
+ | |-<<<NULL>>>
+ | |-DeclStmt
+ | | `-VarDecl '__range1'
+ | | `-DeclRefExpr 'arr'
+ | |-DeclStmt
+ | | `-VarDecl '__begin1'
+ | | `-ImplicitCastExpr
+ | | `-DeclRefExpr '__range1'
+ | |-DeclStmt
+ | | `-VarDecl '__end1'
+ | | `-BinaryOperator
+ | | |-ImplicitCastExpr
+ | | | `-DeclRefExpr '__range1'
+ | | `-IntegerLiteral
+ | |-BinaryOperator
+ | | |-ImplicitCastExpr
+ | | | `-DeclRefExpr '__begin1'
+ | | `-ImplicitCastExpr
+ | | `-DeclRefExpr '__end1'
+ | |-UnaryOperator
+ | | `-DeclRefExpr '__begin1'
+ | |-DeclStmt
+ | | `-VarDecl 'i'
+ | | `-ImplicitCastExpr
+ | | `-UnaryOperator
+ | | `-ImplicitCastExpr
+ | | `-DeclRefExpr '__begin1'
+ | `-CompoundStmt
+ `-CXXForRangeStmt
+ |-DeclStmt
+ | `-VarDecl 'a'
+ | `-DeclRefExpr 'arr'
+ |-DeclStmt
+ | `-VarDecl '__range1'
+ | `-DeclRefExpr 'a'
+ |-DeclStmt
+ | `-VarDecl '__begin1'
+ | `-ImplicitCastExpr
+ | `-DeclRefExpr '__range1'
+ |-DeclStmt
+ | `-VarDecl '__end1'
+ | `-BinaryOperator
+ | |-ImplicitCastExpr
+ | | `-DeclRefExpr '__range1'
+ | `-IntegerLiteral
+ |-BinaryOperator
+ | |-ImplicitCastExpr
+ | | `-DeclRefExpr '__begin1'
+ | `-ImplicitCastExpr
+ | `-DeclRefExpr '__end1'
+ |-UnaryOperator
+ | `-DeclRefExpr '__begin1'
+ |-DeclStmt
+ | `-VarDecl 'i'
+ | `-ImplicitCastExpr
+ | `-UnaryOperator
+ | `-ImplicitCastExpr
+ | `-DeclRefExpr '__begin1'
+ `-CompoundStmt
+)cpp");
+
+ EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource,
+ BN[0].getNodeAs<Decl>("func")),
+ R"cpp(
+FunctionDecl 'forLoop'
+`-CompoundStmt
+ |-DeclStmt
+ | `-VarDecl 'arr'
+ |-CXXForRangeStmt
+ | |-<<<NULL>>>
+ | |-VarDecl 'i'
+ | |-DeclRefExpr 'arr'
+ | `-CompoundStmt
+ `-CXXForRangeStmt
+ |-DeclStmt
+ | `-VarDecl 'a'
+ | `-DeclRefExpr 'arr'
+ |-VarDecl 'i'
+ |-DeclRefExpr 'a'
+ `-CompoundStmt
+)cpp");
+ }
+ {
+ auto BN = ast_matchers::match(
+ cxxRecordDecl(hasName("DefaultedAndDeleted"), unless(isImplicit()))
+ .bind("rec"),
+ AST2->getASTContext());
+ EXPECT_EQ(BN.size(), 1u);
+
+ EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs<Decl>("rec")),
+ R"cpp(
+CXXRecordDecl 'DefaultedAndDeleted'
+|-CXXRecordDecl 'DefaultedAndDeleted'
+|-FieldDecl 'nt'
+|-CXXConstructorDecl 'DefaultedAndDeleted'
+| |-CXXCtorInitializer 'nt'
+| | `-CXXConstructExpr
+| `-CompoundStmt
+|-CXXDestructorDecl '~DefaultedAndDeleted'
+| `-CompoundStmt
+|-CXXConstructorDecl 'DefaultedAndDeleted'
+| `-ParmVarDecl ''
+|-CXXMethodDecl 'operator='
+| |-ParmVarDecl ''
+| `-CompoundStmt
+| |-CXXMemberCallExpr
+| | |-MemberExpr
+| | | `-MemberExpr
+| | | `-CXXThisExpr
+| | `-MemberExpr
+| | `-DeclRefExpr ''
+| `-ReturnStmt
+| `-UnaryOperator
+| `-CXXThisExpr
+|-CXXConstructorDecl 'DefaultedAndDeleted'
+| `-ParmVarDecl ''
+`-CXXMethodDecl 'operator='
+ `-ParmVarDecl ''
+)cpp");
+
+ EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource,
+ BN[0].getNodeAs<Decl>("rec")),
+ R"cpp(
+CXXRecordDecl 'DefaultedAndDeleted'
+|-FieldDecl 'nt'
+|-CXXConstructorDecl 'DefaultedAndDeleted'
+|-CXXDestructorDecl '~DefaultedAndDeleted'
+|-CXXConstructorDecl 'DefaultedAndDeleted'
+| `-ParmVarDecl ''
+|-CXXMethodDecl 'operator='
+| `-ParmVarDecl ''
+|-CXXConstructorDecl 'DefaultedAndDeleted'
+| `-ParmVarDecl ''
+`-CXXMethodDecl 'operator='
+ `-ParmVarDecl ''
+)cpp");
+ }
+ {
+ auto BN = ast_matchers::match(
+ callExpr(callee(functionDecl(hasName("hasDefaultArg"))))
+ .bind("funcCall"),
+ AST2->getASTContext());
+ EXPECT_EQ(BN.size(), 1u);
+
+ EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs<CallExpr>("funcCall")),
+ R"cpp(
+CallExpr
+|-ImplicitCastExpr
+| `-DeclRefExpr 'hasDefaultArg'
+|-IntegerLiteral
+`-CXXDefaultArgExpr
+)cpp");
+ EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource,
+ BN[0].getNodeAs<CallExpr>("funcCall")),
+ R"cpp(
+CallExpr
+|-DeclRefExpr 'hasDefaultArg'
+`-IntegerLiteral
+)cpp");
+ }
+}
+
TEST(Traverse, IgnoreUnlessSpelledInSourceTemplateInstantiations) {
auto AST = buildASTFromCode(R"cpp(
ClassTemplateDecl 'TemplStruct'
|-TemplateTypeParmDecl 'T'
`-CXXRecordDecl 'TemplStruct'
- |-CXXRecordDecl 'TemplStruct'
|-CXXConstructorDecl 'TemplStruct<T>'
| `-CompoundStmt
|-CXXDestructorDecl '~TemplStruct<T>'
ClassTemplateSpecializationDecl 'TemplStruct'
|-TemplateArgument type _Bool
| `-BuiltinType
-|-CXXRecordDecl 'TemplStruct'
|-CXXConstructorDecl 'TemplStruct'
| `-CompoundStmt
|-CXXDestructorDecl '~TemplStruct'
}
}
+TEST(Traversal, traverseNoImplicit) {
+ StringRef Code = R"cpp(
+struct NonTrivial {
+ NonTrivial() {}
+ NonTrivial(const NonTrivial&) {}
+ NonTrivial& operator=(const NonTrivial&) { return *this; }
+
+ ~NonTrivial() {}
+};
+
+struct NoSpecialMethods {
+ NonTrivial nt;
+};
+
+struct ContainsArray {
+ NonTrivial arr[2];
+ ContainsArray& operator=(const ContainsArray &other) = default;
+};
+
+void copyIt()
+{
+ NoSpecialMethods nc1;
+ NoSpecialMethods nc2(nc1);
+ nc2 = nc1;
+
+ ContainsArray ca;
+ ContainsArray ca2;
+ ca2 = ca;
+}
+
+struct HasCtorInits : NoSpecialMethods, NonTrivial
+{
+ int m_i;
+ NonTrivial m_nt;
+ HasCtorInits() : NoSpecialMethods(), m_i(42) {}
+};
+
+)cpp";
+ {
+ auto M = cxxRecordDecl(hasName("NoSpecialMethods"),
+ has(cxxRecordDecl(hasName("NoSpecialMethods"))));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+
+ M = cxxRecordDecl(hasName("NoSpecialMethods"),
+ has(cxxConstructorDecl(isCopyConstructor())));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+
+ M = cxxRecordDecl(hasName("NoSpecialMethods"),
+ has(cxxMethodDecl(isCopyAssignmentOperator())));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+
+ M = cxxRecordDecl(hasName("NoSpecialMethods"),
+ has(cxxConstructorDecl(isDefaultConstructor())));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+
+ M = cxxRecordDecl(hasName("NoSpecialMethods"), has(cxxDestructorDecl()));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ // Compiler generates a forStmt to copy the array
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, forStmt())));
+ EXPECT_FALSE(
+ matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, forStmt())));
+ }
+ {
+ // The defaulted method declaration can be matched, but not its
+ // definition, in IgnoreUnlessSpelledInSource mode
+ auto MDecl = cxxMethodDecl(ofClass(cxxRecordDecl(hasName("ContainsArray"))),
+ isCopyAssignmentOperator(), isDefaulted());
+
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, MDecl)));
+ EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MDecl)));
+
+ auto MDef = cxxMethodDecl(MDecl, has(compoundStmt()));
+
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, MDef)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MDef)));
+
+ // The parameter of the defaulted method can still be matched.
+ auto MParm =
+ cxxMethodDecl(MDecl, hasParameter(0, parmVarDecl(hasName("other"))));
+
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, MParm)));
+ EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MParm)));
+ }
+ {
+ auto M =
+ cxxConstructorDecl(hasName("HasCtorInits"),
+ has(cxxCtorInitializer(forField(hasName("m_i")))));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ auto M =
+ cxxConstructorDecl(hasName("HasCtorInits"),
+ has(cxxCtorInitializer(forField(hasName("m_nt")))));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ auto M = cxxConstructorDecl(
+ hasName("HasCtorInits"),
+ has(cxxCtorInitializer(withInitializer(cxxConstructExpr(hasDeclaration(
+ cxxConstructorDecl(hasName("NoSpecialMethods"))))))));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ auto M = cxxConstructorDecl(
+ hasName("HasCtorInits"),
+ has(cxxCtorInitializer(withInitializer(cxxConstructExpr(
+ hasDeclaration(cxxConstructorDecl(hasName("NonTrivial"))))))));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ auto M = cxxCtorInitializer(forField(hasName("m_nt")));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+
+ Code = R"cpp(
+ void rangeFor()
+ {
+ int arr[2];
+ for (auto i : arr)
+ {
+
+ }
+ }
+ )cpp";
+ {
+ auto M = cxxForRangeStmt(has(binaryOperator(hasOperatorName("!="))));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ auto M =
+ cxxForRangeStmt(hasDescendant(binaryOperator(hasOperatorName("+"))));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ auto M =
+ cxxForRangeStmt(hasDescendant(unaryOperator(hasOperatorName("++"))));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ auto M = cxxForRangeStmt(has(declStmt()));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ auto M =
+ cxxForRangeStmt(hasLoopVariable(varDecl(hasName("i"))),
+ hasRangeInit(declRefExpr(to(varDecl(hasName("arr"))))));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ auto M = cxxForRangeStmt(unless(hasInitStatement(stmt())));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ auto M = cxxForRangeStmt(hasBody(stmt()));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+
+ Code = R"cpp(
+ void rangeFor()
+ {
+ int arr[2];
+ for (auto& a = arr; auto i : a)
+ {
+
+ }
+ }
+ )cpp";
+ {
+ auto M = cxxForRangeStmt(has(binaryOperator(hasOperatorName("!="))));
+ EXPECT_TRUE(
+ matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"}));
+ EXPECT_FALSE(
+ matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M),
+ true, {"-std=c++20"}));
+ }
+ {
+ auto M =
+ cxxForRangeStmt(hasDescendant(binaryOperator(hasOperatorName("+"))));
+ EXPECT_TRUE(
+ matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"}));
+ EXPECT_FALSE(
+ matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M),
+ true, {"-std=c++20"}));
+ }
+ {
+ auto M =
+ cxxForRangeStmt(hasDescendant(unaryOperator(hasOperatorName("++"))));
+ EXPECT_TRUE(
+ matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"}));
+ EXPECT_FALSE(
+ matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M),
+ true, {"-std=c++20"}));
+ }
+ {
+ auto M = cxxForRangeStmt(has(declStmt()));
+ EXPECT_TRUE(
+ matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"}));
+ EXPECT_FALSE(
+ matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M),
+ true, {"-std=c++20"}));
+ }
+ {
+ auto M = cxxForRangeStmt(
+ hasInitStatement(declStmt(hasSingleDecl(varDecl(
+ hasName("a"),
+ hasInitializer(declRefExpr(to(varDecl(hasName("arr"))))))))),
+ hasLoopVariable(varDecl(hasName("i"))),
+ hasRangeInit(declRefExpr(to(varDecl(hasName("a"))))));
+ EXPECT_TRUE(
+ matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"}));
+ EXPECT_TRUE(
+ matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M),
+ true, {"-std=c++20"}));
+ }
+ Code = R"cpp(
+void hasDefaultArg(int i, int j = 0)
+{
+}
+void callDefaultArg()
+{
+ hasDefaultArg(42);
+}
+)cpp";
+ {
+ auto M = callExpr(has(integerLiteral(equals(42))));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+ {
+ auto M = callExpr(has(cxxDefaultArgExpr()));
+ EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M)));
+ EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M)));
+ }
+}
+
template <typename MatcherT>
bool matcherTemplateWithBinding(StringRef Code, const MatcherT &M) {
return matchAndVerifyResultTrue(
compareSnippets(Expected, rewrite(Input));
}
+ template <typename R> void testRuleFailure(R Rule, StringRef Input) {
+ Transformers.push_back(
+ std::make_unique<Transformer>(std::move(Rule), consumer()));
+ Transformers.back()->registerMatchers(&MatchFinder);
+ ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code";
+ }
+
// Transformers are referenced by MatchFinder.
std::vector<std::unique_ptr<Transformer>> Transformers;
clang::ast_matchers::MatchFinder MatchFinder;
EXPECT_EQ(ErrorCount, 0);
}
+TEST_F(TransformerTest, ImplicitNodes_ConstructorDecl) {
+
+ std::string OtherStructPrefix = R"cpp(
+struct Other {
+)cpp";
+ std::string OtherStructSuffix = "};";
+
+ std::string CopyableStructName = "struct Copyable";
+ std::string BrokenStructName = "struct explicit Copyable";
+
+ std::string CodeSuffix = R"cpp(
+{
+ Other m_i;
+ Copyable();
+};
+)cpp";
+
+ std::string CopyCtor = "Other(const Other&) = default;";
+ std::string ExplicitCopyCtor = "explicit Other(const Other&) = default;";
+ std::string BrokenExplicitCopyCtor =
+ "explicit explicit explicit Other(const Other&) = default;";
+
+ std::string RewriteInput = OtherStructPrefix + CopyCtor + OtherStructSuffix +
+ CopyableStructName + CodeSuffix;
+ std::string ExpectedRewriteOutput = OtherStructPrefix + ExplicitCopyCtor +
+ OtherStructSuffix + CopyableStructName +
+ CodeSuffix;
+ std::string BrokenRewriteOutput = OtherStructPrefix + BrokenExplicitCopyCtor +
+ OtherStructSuffix + BrokenStructName +
+ CodeSuffix;
+
+ auto MatchedRecord =
+ cxxConstructorDecl(isCopyConstructor()).bind("copyConstructor");
+
+ auto RewriteRule =
+ changeTo(before(node("copyConstructor")), cat("explicit "));
+
+ testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedRecord),
+ RewriteRule),
+ RewriteInput, ExpectedRewriteOutput);
+
+ testRule(makeRule(traverse(TK_AsIs, MatchedRecord), RewriteRule),
+ RewriteInput, BrokenRewriteOutput);
+}
+
+TEST_F(TransformerTest, ImplicitNodes_RangeFor) {
+
+ std::string CodePrefix = R"cpp(
+struct Container
+{
+ int* begin() const;
+ int* end() const;
+ int* cbegin() const;
+ int* cend() const;
+};
+
+void foo()
+{
+ const Container c;
+)cpp";
+
+ std::string BeginCallBefore = " c.begin();";
+ std::string BeginCallAfter = " c.cbegin();";
+
+ std::string ForLoop = "for (auto i : c)";
+ std::string BrokenForLoop = "for (auto i :.cbegin() c)";
+
+ std::string CodeSuffix = R"cpp(
+ {
+ }
+}
+)cpp";
+
+ std::string RewriteInput =
+ CodePrefix + BeginCallBefore + ForLoop + CodeSuffix;
+ std::string ExpectedRewriteOutput =
+ CodePrefix + BeginCallAfter + ForLoop + CodeSuffix;
+ std::string BrokenRewriteOutput =
+ CodePrefix + BeginCallAfter + BrokenForLoop + CodeSuffix;
+
+ auto MatchedRecord =
+ cxxMemberCallExpr(on(expr(hasType(qualType(isConstQualified(),
+ hasDeclaration(cxxRecordDecl(
+ hasName("Container"))))))
+ .bind("callTarget")),
+ callee(cxxMethodDecl(hasName("begin"))))
+ .bind("constBeginCall");
+
+ auto RewriteRule =
+ changeTo(node("constBeginCall"), cat(name("callTarget"), ".cbegin()"));
+
+ testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedRecord),
+ RewriteRule),
+ RewriteInput, ExpectedRewriteOutput);
+
+ testRule(makeRule(traverse(TK_AsIs, MatchedRecord), RewriteRule),
+ RewriteInput, BrokenRewriteOutput);
+}
+
+TEST_F(TransformerTest, ImplicitNodes_ForStmt) {
+
+ std::string CodePrefix = R"cpp(
+struct NonTrivial {
+ NonTrivial() {}
+ NonTrivial(NonTrivial&) {}
+ NonTrivial& operator=(NonTrivial const&) { return *this; }
+
+ ~NonTrivial() {}
+};
+
+struct ContainsArray {
+ NonTrivial arr[2];
+ ContainsArray& operator=(ContainsArray const&) = default;
+};
+
+void testIt()
+{
+ ContainsArray ca1;
+ ContainsArray ca2;
+ ca2 = ca1;
+)cpp";
+
+ auto CodeSuffix = "}";
+
+ auto LoopBody = R"cpp(
+ {
+
+ }
+)cpp";
+
+ auto RawLoop = "for (auto i = 0; i != 5; ++i)";
+
+ auto RangeLoop = "for (auto i : boost::irange(5))";
+
+ // Expect to rewrite the raw loop to the ranged loop.
+ // This works in TK_IgnoreUnlessSpelledInSource mode, but TK_AsIs
+ // mode also matches the hidden for loop generated in the copy assignment
+ // operator of ContainsArray. Transformer then fails to transform the code at
+ // all.
+
+ auto RewriteInput =
+ CodePrefix + RawLoop + LoopBody + RawLoop + LoopBody + CodeSuffix;
+
+ auto RewriteOutput =
+ CodePrefix + RangeLoop + LoopBody + RangeLoop + LoopBody + CodeSuffix;
+ {
+ auto MatchedLoop = forStmt(
+ has(declStmt(
+ hasSingleDecl(varDecl(hasInitializer(integerLiteral(equals(0))))
+ .bind("loopVar")))),
+ has(binaryOperator(hasOperatorName("!="),
+ hasLHS(ignoringImplicit(declRefExpr(
+ to(varDecl(equalsBoundNode("loopVar")))))),
+ hasRHS(expr().bind("upperBoundExpr")))),
+ has(unaryOperator(hasOperatorName("++"),
+ hasUnaryOperand(declRefExpr(
+ to(varDecl(equalsBoundNode("loopVar"))))))
+ .bind("incrementOp")));
+
+ auto RewriteRule =
+ changeTo(transformer::enclose(node("loopVar"), node("incrementOp")),
+ cat("auto ", name("loopVar"), " : boost::irange(",
+ node("upperBoundExpr"), ")"));
+
+ testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedLoop),
+ RewriteRule),
+ RewriteInput, RewriteOutput);
+
+ testRuleFailure(makeRule(traverse(TK_AsIs, MatchedLoop), RewriteRule),
+ RewriteInput);
+ }
+}
+
TEST_F(TransformerTest, TemplateInstantiation) {
std::string NonTemplatesInput = R"cpp(