[clang] Add 'forNone' and 'forNoDescendant' AST matchers (PR #86230)

June Rhodes via cfe-commits cfe-commits at lists.llvm.org
Thu Mar 21 19:41:45 PDT 2024


https://github.com/hach-que updated https://github.com/llvm/llvm-project/pull/86230

>From 25e3b11324ba4fc43e36035d357d1aa785898bbc Mon Sep 17 00:00:00 2001
From: June Rhodes <jrhodes at redpoint.games>
Date: Fri, 22 Mar 2024 13:07:57 +1100
Subject: [PATCH] Add 'forNone' and 'forNoDescendant' AST matchers

---
 clang/include/clang/ASTMatchers/ASTMatchers.h | 15 +++
 .../clang/ASTMatchers/ASTMatchersInternal.h   | 98 +++++++++++++++++++
 clang/lib/ASTMatchers/ASTMatchersInternal.cpp | 13 +++
 clang/lib/ASTMatchers/Dynamic/Registry.cpp    |  2 +
 4 files changed, 128 insertions(+)

diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h
index 2f71053d030f68..fa6afe2f02f6b0 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchers.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchers.h
@@ -3547,6 +3547,21 @@ extern const internal::ArgumentAdaptingMatcherFunc<
     internal::ForEachDescendantMatcher>
     forEachDescendant;
 
+/// Matches AST nodes that have no child AST nodes that match the
+/// provided matcher.
+///
+/// Usable as: Any Matcher
+extern const internal::ArgumentAdaptingMatcherFunc<internal::ForNoneMatcher>
+    forNone;
+
+/// Matches AST nodes that have no descendant AST nodes that match the
+/// provided matcher.
+///
+/// Usable as: Any Matcher
+extern const internal::ArgumentAdaptingMatcherFunc<
+    internal::ForNoDescendantMatcher>
+    forNoDescendant;
+
 /// Matches if the node or any descendant matches.
 ///
 /// Generates results for each match.
diff --git a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
index 47d912c73dd7eb..b40f0b77b042db 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
@@ -268,6 +268,26 @@ class BoundNodesMap {
     return true;
   }
 
+  /// Returns \c true if the \c BoundNodesMap entirely contains the values
+  /// in \c Subset.
+  bool contains(const BoundNodesMap &Subset) {
+    const auto &N = this->NodeMap.end();
+    if (Subset.NodeMap.size() == 1) {
+      // Avoid iteration if the subset only has a single value.
+      const auto &F = Subset.NodeMap.begin();
+      const auto &T = this->NodeMap.find(F->first);
+      return T != N && T->second == F->second;
+    } else {
+      for (const auto &F : Subset.NodeMap) {
+        const auto &T = this->NodeMap.find(F.first);
+        if (T == N || T->second != F.second) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
 private:
   IDToNodeMap NodeMap;
 };
@@ -306,6 +326,10 @@ class BoundNodesTreeBuilder {
   /// The ownership of 'ResultVisitor' remains at the caller.
   void visitMatches(Visitor* ResultVisitor);
 
+  /// Returns true if any of the entries in this tree contain the
+  /// other bound nodes map.
+  bool contains(const internal::BoundNodesMap &Subset);
+
   template <typename ExcludePredicate>
   bool removeBindings(const ExcludePredicate &Predicate) {
     llvm::erase_if(Bindings, Predicate);
@@ -1626,6 +1650,42 @@ class ForEachMatcher : public MatcherInterface<T> {
   }
 };
 
+/// Matches nodes of type T that have no child nodes of type ChildT for
+/// which a specified child matcher matches. ChildT must be an AST base
+/// type.
+/// ForNoneMatcher will only match if none of the child nodes match
+/// the inner matcher.
+template <typename T, typename ChildT>
+class ForNoneMatcher : public MatcherInterface<T> {
+  static_assert(IsBaseType<ChildT>::value,
+                "for none only accepts base type matcher");
+
+  DynTypedMatcher InnerMatcher;
+
+public:
+  explicit ForNoneMatcher(const Matcher<ChildT> &InnerMatcher)
+      : InnerMatcher(InnerMatcher) {}
+
+  bool matches(const T &Node, ASTMatchFinder *Finder,
+               BoundNodesTreeBuilder *Builder) const override {
+    BoundNodesTreeBuilder MatchingBuilder(*Builder);
+    bool AnyMatched = Finder->matchesChildOf(
+        Node, this->InnerMatcher, &MatchingBuilder, ASTMatchFinder::BK_All);
+    if (!AnyMatched) {
+      // We didn't iterate over any nodes that matched, so
+      // Builder would be empty. This is a success case.
+      return true;
+    }
+    // Otherwise remove from Builder any entries that we
+    // also have in MatchingBuilder because we want to leave
+    // only the remaining entries.
+    return Builder->removeBindings(
+        [&MatchingBuilder](const internal::BoundNodesMap &Nodes) {
+          return MatchingBuilder.contains(Nodes);
+        });
+  }
+};
+
 /// @}
 
 template <typename T>
@@ -1724,6 +1784,44 @@ class ForEachDescendantMatcher : public MatcherInterface<T> {
   }
 };
 
+/// Matches nodes of type T that have no descendant node of
+/// type DescendantT for which the given inner matcher matches.
+///
+/// DescendantT must be an AST base type.
+/// ForNoDescendantMatcher only matches if none of the descendant nodes
+/// match.
+template <typename T, typename DescendantT>
+class ForNoDescendantMatcher : public MatcherInterface<T> {
+  static_assert(IsBaseType<DescendantT>::value,
+                "for no descendant only accepts base type matcher");
+
+  DynTypedMatcher DescendantMatcher;
+
+public:
+  explicit ForNoDescendantMatcher(const Matcher<DescendantT> &DescendantMatcher)
+      : DescendantMatcher(DescendantMatcher) {}
+
+  bool matches(const T &Node, ASTMatchFinder *Finder,
+               BoundNodesTreeBuilder *Builder) const override {
+    BoundNodesTreeBuilder MatchingBuilder(*Builder);
+    bool AnyMatched = 
+        Finder->matchesDescendantOf(Node, this->DescendantMatcher,
+                                    &MatchingBuilder, ASTMatchFinder::BK_All);
+    if (!AnyMatched) {
+      // We didn't iterate over any nodes that matched, so
+      // Builder would be empty. This is a success case.
+      return true;
+    }
+    // Otherwise remove from Builder any entries that we
+    // also have in MatchingBuilder because we want to leave
+    // only the remaining entries.
+    return Builder->removeBindings(
+        [&MatchingBuilder](const internal::BoundNodesMap &Nodes) {
+          return MatchingBuilder.contains(Nodes);
+        });
+  }
+};
+
 /// Matches on nodes that have a getValue() method if getValue() equals
 /// the value the ValueEqualsMatcher was constructed with.
 template <typename T, typename ValueT>
diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
index bf87b1aa0992a5..b25525683906f8 100644
--- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
+++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
@@ -106,6 +106,15 @@ void BoundNodesTreeBuilder::visitMatches(Visitor *ResultVisitor) {
   }
 }
 
+bool BoundNodesTreeBuilder::contains(const internal::BoundNodesMap &Subset) {
+  for (BoundNodesMap &Binding : Bindings) {
+    if (Binding.contains(Subset)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 namespace {
 
 using VariadicOperatorFunction = bool (*)(
@@ -1022,8 +1031,12 @@ const internal::ArgumentAdaptingMatcherFunc<internal::HasDescendantMatcher>
     hasDescendant = {};
 const internal::ArgumentAdaptingMatcherFunc<internal::ForEachMatcher> forEach =
     {};
+const internal::ArgumentAdaptingMatcherFunc<internal::ForNoneMatcher> forNone =
+    {};
 const internal::ArgumentAdaptingMatcherFunc<internal::ForEachDescendantMatcher>
     forEachDescendant = {};
+const internal::ArgumentAdaptingMatcherFunc<internal::ForNoDescendantMatcher>
+    forNoDescendant = {};
 const internal::ArgumentAdaptingMatcherFunc<
     internal::HasParentMatcher,
     internal::TypeList<Decl, NestedNameSpecifierLoc, Stmt, TypeLoc, Attr>,
diff --git a/clang/lib/ASTMatchers/Dynamic/Registry.cpp b/clang/lib/ASTMatchers/Dynamic/Registry.cpp
index 2c75e6beb74301..5bbafb44af895b 100644
--- a/clang/lib/ASTMatchers/Dynamic/Registry.cpp
+++ b/clang/lib/ASTMatchers/Dynamic/Registry.cpp
@@ -259,6 +259,8 @@ RegistryMaps::RegistryMaps() {
   REGISTER_MATCHER(forEachOverridden);
   REGISTER_MATCHER(forEachSwitchCase);
   REGISTER_MATCHER(forEachTemplateArgument);
+  REGISTER_MATCHER(forNone);
+  REGISTER_MATCHER(forNoDescendant);
   REGISTER_MATCHER(forField);
   REGISTER_MATCHER(forFunction);
   REGISTER_MATCHER(forStmt);



More information about the cfe-commits mailing list