[clang] 246b428 - [AST] Ignore implicit nodes in IgnoreUnlessSpelledInSource mode

Stephen Kelly via cfe-commits cfe-commits at lists.llvm.org
Tue Nov 17 08:50:45 PST 2020


Author: Stephen Kelly
Date: 2020-11-17T16:30:07Z
New Revision: 246b428fb3b5d58685064966bcea6592f8839b7e

URL: https://github.com/llvm/llvm-project/commit/246b428fb3b5d58685064966bcea6592f8839b7e
DIFF: https://github.com/llvm/llvm-project/commit/246b428fb3b5d58685064966bcea6592f8839b7e.diff

LOG: [AST] Ignore implicit nodes in IgnoreUnlessSpelledInSource mode

Update the ASTNodeTraverser to dump only nodes spelled in source.  There
are only a few which need to be handled, but Decl nodes for which
isImplicit() is true are handled together.

Update the RAV instances used in ASTMatchFinder to ignore the nodes too.
As with handling of template instantiations, it is necessary to allow
the RAV to process the implicit nodes because they need to be visitable
before the first traverse() matcher is encountered.  An exception to
this is in the MatchChildASTVisitor, because we sometimes wish to make a
node matchable but make its children not-matchable.  This is the case
for defaulted CXXMethodDecls for example.

Extend TransformerTests to illustrate the kinds of problems that can
arise when performing source code rewriting due to matching implicit
nodes.

This change accounts for handling nodes not spelled in source when using
direct matching of nodes, and when using the has() and hasDescendant()
matchers.  Other matchers such as
cxxRecordDecl(hasMethod(cxxMethodDecl())) still succeed for
compiler-generated methods for example after this change.  Updating the
implementations of hasMethod() and other matchers is for a follow-up
patch.

Differential Revision: https://reviews.llvm.org/D90982

Added: 
    

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/AST/ASTNodeTraverser.h
    clang/lib/ASTMatchers/ASTMatchFinder.cpp
    clang/unittests/AST/ASTTraverserTest.cpp
    clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
    clang/unittests/Tooling/TransformerTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index a3a68b796e3b..8da9490f7b6f 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -230,7 +230,9 @@ release of Clang. Users of the build system should adjust accordingly.
 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
 ------------

diff  --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index 78b2ec5a3dc7..4f33b0c67e94 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -85,6 +85,9 @@ class ASTNodeTraverser
   TraversalKind GetTraversalKind() const { return Traversal; }
 
   void Visit(const Decl *D) {
+    if (Traversal == TK_IgnoreUnlessSpelledInSource && D->isImplicit())
+      return;
+
     getNodeDelegate().AddChild([=] {
       getNodeDelegate().Visit(D);
       if (!D)
@@ -144,7 +147,8 @@ class ASTNodeTraverser
       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())
@@ -185,6 +189,8 @@ class ASTNodeTraverser
   }
 
   void Visit(const CXXCtorInitializer *Init) {
+    if (Traversal == TK_IgnoreUnlessSpelledInSource && !Init->isWritten())
+      return;
     getNodeDelegate().AddChild([=] {
       getNodeDelegate().Visit(Init);
       Visit(Init->getInit());
@@ -401,6 +407,9 @@ class ASTNodeTraverser
     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);
@@ -417,6 +426,9 @@ class ASTNodeTraverser
   }
 
   void VisitVarDecl(const VarDecl *D) {
+    if (Traversal == TK_IgnoreUnlessSpelledInSource && D->isCXXForRangeDecl())
+      return;
+
     if (D->hasInit())
       Visit(D->getInit());
   }
@@ -717,6 +729,26 @@ class ASTNodeTraverser
       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());
   }

diff  --git a/clang/lib/ASTMatchers/ASTMatchFinder.cpp b/clang/lib/ASTMatchers/ASTMatchFinder.cpp
index cb282c9225fb..e43778b4fe8f 100644
--- a/clang/lib/ASTMatchers/ASTMatchFinder.cpp
+++ b/clang/lib/ASTMatchers/ASTMatchFinder.cpp
@@ -95,9 +95,12 @@ class MatchChildASTVisitor
   // 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
@@ -145,6 +148,11 @@ class MatchChildASTVisitor
   // 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);
   }
@@ -176,6 +184,10 @@ class MatchChildASTVisitor
     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);
@@ -265,7 +277,7 @@ class MatchChildASTVisitor
   }
 
   bool shouldVisitTemplateInstantiations() const { return true; }
-  bool shouldVisitImplicitCode() const { return true; }
+  bool shouldVisitImplicitCode() const { return !IgnoreImplicitChildren; }
 
 private:
   // Used for updating the depth during traversal.
@@ -360,6 +372,7 @@ class MatchChildASTVisitor
   int CurrentDepth;
   const int MaxDepth;
   const TraversalKind Traversal;
+  const bool IgnoreImplicitChildren;
   const ASTMatchFinder::BindKind Bind;
   bool Matches;
 };
@@ -497,19 +510,21 @@ class MatchASTVisitor : public RecursiveASTVisitor<MatchASTVisitor>,
                           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);
   }
 
@@ -616,6 +631,7 @@ class MatchASTVisitor : public RecursiveASTVisitor<MatchASTVisitor>,
 
 private:
   bool TraversingASTNodeNotSpelledInSource = false;
+  bool TraversingASTChildrenNotSpelledInSource = false;
 
   struct ASTNodeNotSpelledInSourceScope {
     ASTNodeNotSpelledInSourceScope(MatchASTVisitor *V, bool B)
@@ -631,6 +647,20 @@ class MatchASTVisitor : public RecursiveASTVisitor<MatchASTVisitor>,
     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) {}
@@ -1050,6 +1080,24 @@ bool MatchASTVisitor::TraverseDecl(Decl *DeclNode) {
   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);
 }
@@ -1058,6 +1106,10 @@ bool MatchASTVisitor::TraverseStmt(Stmt *StmtNode, DataRecursionQueue *Queue) {
   if (!StmtNode) {
     return true;
   }
+  bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
+                         TraversingASTChildrenNotSpelledInSource;
+
+  ASTNodeNotSpelledInSourceScope RAII(this, ScopedTraversal);
   match(*StmtNode);
   return RecursiveASTVisitor<MatchASTVisitor>::TraverseStmt(StmtNode, Queue);
 }
@@ -1103,6 +1155,14 @@ bool MatchASTVisitor::TraverseConstructorInitializer(
   if (!CtorInit)
     return true;
 
+  bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
+                         TraversingASTChildrenNotSpelledInSource;
+
+  if (!CtorInit->isWritten())
+    ScopedTraversal = true;
+
+  ASTNodeNotSpelledInSourceScope RAII1(this, ScopedTraversal);
+
   match(*CtorInit);
 
   return RecursiveASTVisitor<MatchASTVisitor>::TraverseConstructorInitializer(

diff  --git a/clang/unittests/AST/ASTTraverserTest.cpp b/clang/unittests/AST/ASTTraverserTest.cpp
index 5e167e4d30b7..e61069ae61d6 100644
--- a/clang/unittests/AST/ASTTraverserTest.cpp
+++ b/clang/unittests/AST/ASTTraverserTest.cpp
@@ -35,6 +35,10 @@ class NodeTreePrinter : public TextTreeStructure {
   }
 
   void Visit(const Stmt *S) {
+    if (!S) {
+      OS << "<<<NULL>>>";
+      return;
+    }
     OS << S->getStmtClassName();
     if (auto *E = dyn_cast<DeclRefExpr>(S)) {
       OS << " '" << E->getDecl()->getDeclName() << "'";
@@ -51,7 +55,14 @@ class NodeTreePrinter : public TextTreeStructure {
     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()) {
@@ -226,7 +237,7 @@ WarnUnusedResultAttr
 
   verifyWithDynNode(Init,
                     R"cpp(
-CXXCtorInitializer
+CXXCtorInitializer 'm_number'
 `-IntegerLiteral
 )cpp");
 
@@ -1050,6 +1061,438 @@ LambdaExpr
   }
 }
 
+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(
@@ -1108,7 +1551,6 @@ template<> bool timesTwo<bool>(bool) {
 ClassTemplateDecl 'TemplStruct'
 |-TemplateTypeParmDecl 'T'
 `-CXXRecordDecl 'TemplStruct'
-  |-CXXRecordDecl 'TemplStruct'
   |-CXXConstructorDecl 'TemplStruct<T>'
   | `-CompoundStmt
   |-CXXDestructorDecl '~TemplStruct<T>'
@@ -1216,7 +1658,6 @@ ClassTemplateSpecializationDecl 'TemplStruct'
 ClassTemplateSpecializationDecl 'TemplStruct'
 |-TemplateArgument type _Bool
 | `-BuiltinType
-|-CXXRecordDecl 'TemplStruct'
 |-CXXConstructorDecl 'TemplStruct'
 | `-CompoundStmt
 |-CXXDestructorDecl '~TemplStruct'

diff  --git a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
index 0d06bdce7f34..7c4ba9bcc2a6 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
@@ -2331,6 +2331,260 @@ template<> bool timesTwo<bool>(bool){
   }
 }
 
+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(

diff  --git a/clang/unittests/Tooling/TransformerTest.cpp b/clang/unittests/Tooling/TransformerTest.cpp
index 1f0637446a00..f70dec414b8d 100644
--- a/clang/unittests/Tooling/TransformerTest.cpp
+++ b/clang/unittests/Tooling/TransformerTest.cpp
@@ -132,6 +132,13 @@ class ClangRefactoringTestBase : public testing::Test {
     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;
@@ -1067,6 +1074,179 @@ TEST_F(TransformerTest, ErrorOccurredMatchSkipped) {
   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(


        


More information about the cfe-commits mailing list