[clang] [clang][ASTMatcher] Add forEachAdjacentSubstatements matcher (PR #173587)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Dec 25 17:27:14 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Denis Mikhailov (denzor200)
<details>
<summary>Changes</summary>
Implementation of this matcher was mostly generated by Cursor.
This matcher(like [hasAdjacentSubstatements](https://github.com/llvm/llvm-project/pull/169965)) needed for me at least to implement https://github.com/llvm/llvm-project/issues/133110 and https://github.com/llvm/llvm-project/issues/38471
Also maybe we will use it in https://github.com/llvm/llvm-project/pull/158462
---
Patch is 33.91 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/173587.diff
8 Files Affected:
- (modified) clang/docs/LibASTMatchersReference.html (+31)
- (modified) clang/docs/ReleaseNotes.rst (+1)
- (modified) clang/include/clang/ASTMatchers/ASTMatchers.h (+20)
- (modified) clang/include/clang/ASTMatchers/ASTMatchersInternal.h (+61-5)
- (modified) clang/lib/ASTMatchers/ASTMatchFinder.cpp (+31-12)
- (modified) clang/lib/ASTMatchers/ASTMatchersInternal.cpp (+95-4)
- (modified) clang/lib/ASTMatchers/Dynamic/Registry.cpp (+1)
- (modified) clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp (+254)
``````````diff
diff --git a/clang/docs/LibASTMatchersReference.html b/clang/docs/LibASTMatchersReference.html
index e34ac30b8f5a4..cb65552751485 100644
--- a/clang/docs/LibASTMatchersReference.html
+++ b/clang/docs/LibASTMatchersReference.html
@@ -8211,6 +8211,22 @@ <h2 id="traversal-matchers">AST Traversal Matchers</h2>
</pre></td></tr>
+<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CompoundStmt.html">CompoundStmt</a>></td><td class="name" onclick="toggle('forEachAdjacentSubstatements0')"><a name="forEachAdjacentSubstatements0Anchor">forEachAdjacentSubstatements</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>>, ..., Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>></td></tr>
+<tr><td colspan="4" class="doc" id="forEachAdjacentSubstatements0"><pre>Matches compound statements where all sets of adjacent substatements
+matching the provided sequence of matchers are found. Also matches
+StmtExprs that have CompoundStmt as children.
+
+Given
+ { {}; 1+2; {}; 3+4; }
+forEachAdjacentSubstatements(compoundStmt(), binaryOperator())
+ matches '{ {}; 1+2; {}; 3+4; }' twice
+with compoundStmt()
+ matching '{}' (first match) and '{}' (second match)
+with binaryOperator()
+ matching '1+2' (first match) and '3+4' (second match)
+</pre></td></tr>
+
+
<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CoroutineBodyStmt.html">CoroutineBodyStmt</a>></td><td class="name" onclick="toggle('hasBody5')"><a name="hasBody5Anchor">hasBody</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>> InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="hasBody5"><pre>Matches a 'for', 'while', 'while' statement or a function or coroutine
definition that has a given body. Note that in case of functions or
@@ -9991,6 +10007,21 @@ <h2 id="traversal-matchers">AST Traversal Matchers</h2>
</pre></td></tr>
+<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1StmtExpr.html">StmtExpr</a>></td><td class="name" onclick="toggle('forEachAdjacentSubstatements1')"><a name="forEachAdjacentSubstatements1Anchor">forEachAdjacentSubstatements</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>>, ..., Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>></td></tr>
+<tr><td colspan="4" class="doc" id="forEachAdjacentSubstatements1"><pre>Matches StmtExprs with compound statements containing all sets of
+adjacent substatements that match the provided sequence of matchers.
+
+Given
+ int x = ({ {}; 1+2; {}; 3+4; });
+forEachAdjacentSubstatements(compoundStmt(), binaryOperator())
+ matches '({ {}; 1+2; {}; 3+4; })' twice
+with compoundStmt()
+ matching '{}' (first match) and '{}' (second match)
+with binaryOperator()
+ matching '1+2' (first match) and '3+4' (second match)
+</pre></td></tr>
+
+
<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>></td><td class="name" onclick="toggle('alignOfExpr0')"><a name="alignOfExpr0Anchor">alignOfExpr</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1UnaryExprOrTypeTraitExpr.html">UnaryExprOrTypeTraitExpr</a>> InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="alignOfExpr0"><pre>Same as unaryExprOrTypeTraitExpr, but only matching
alignof.
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index feaf92ad4415f..6c925412fc334 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -753,6 +753,7 @@ AST Matchers
- Added ``hasExplicitParameters`` for ``LambdaExpr`` as an output attribute to
AST JSON dumps.
- Add ``arrayTypeLoc`` matcher for matching ``ArrayTypeLoc``.
+- Add ``forEachAdjacentSubstatements``.
clang-format
------------
diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h
index e3ec26207d2bc..5da86716587e5 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchers.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchers.h
@@ -5911,6 +5911,26 @@ AST_POLYMORPHIC_MATCHER_P(hasAnySubstatement,
Builder) != CS->body_end();
}
+
+/// Matches compound statements where one ore more sets of adjacent
+/// substatements matching the provided sequence of matchers. Also matches
+/// StmtExprs that have CompoundStmt as children.
+///
+/// Given
+/// \code
+/// { {}; 1+2; {}; 3+4; }
+/// \endcode
+/// forEachAdjacentSubstatements(compoundStmt(), binaryOperator())
+/// matches '{ {}; 1+2; {}; 3+4; }' twice
+/// with compoundStmt()
+/// matching '{}' (first match) and '{}' (second match)
+/// with binaryOperator()
+/// matching '1+2' (first match) and '3+4' (second match)
+extern const internal::VariadicFunction<
+ internal::ForEachAdjacentSubstatementsMatcherType, internal::Matcher<Stmt>,
+ internal::forEachAdjSubstatementsFunc>
+ forEachAdjacentSubstatements;
+
/// Checks that a compound statement contains a specific number of
/// child statements.
///
diff --git a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
index c050fb7d797e3..f16c2e32cb6c2 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
@@ -47,6 +47,7 @@
#include "clang/AST/TemplateName.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
+#include "clang/ASTMatchers/ASTMatchersMacros.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/OperatorKinds.h"
#include "llvm/ADT/APFloat.h"
@@ -410,9 +411,10 @@ class DynTypedMatcher {
public:
/// Takes ownership of the provided implementation pointer.
template <typename T>
- DynTypedMatcher(MatcherInterface<T> *Implementation)
+ DynTypedMatcher(const MatcherInterface<T> *Implementation)
: SupportedKind(ASTNodeKind::getFromNodeKind<T>()),
- RestrictKind(SupportedKind), Implementation(Implementation) {}
+ RestrictKind(SupportedKind),
+ Implementation(Implementation) {}
/// Construct from a variadic function.
enum VariadicOperator {
@@ -541,7 +543,7 @@ class DynTypedMatcher {
private:
DynTypedMatcher(ASTNodeKind SupportedKind, ASTNodeKind RestrictKind,
- IntrusiveRefCntPtr<DynMatcherInterface> Implementation)
+ IntrusiveRefCntPtr<const DynMatcherInterface> Implementation)
: SupportedKind(SupportedKind), RestrictKind(RestrictKind),
Implementation(std::move(Implementation)) {}
@@ -553,7 +555,7 @@ class DynTypedMatcher {
/// It allows to perform implicit and dynamic cast of matchers without
/// needing to change \c Implementation.
ASTNodeKind RestrictKind;
- IntrusiveRefCntPtr<DynMatcherInterface> Implementation;
+ IntrusiveRefCntPtr<const DynMatcherInterface> Implementation;
};
/// Wrapper of a MatcherInterface<T> *that allows copying.
@@ -743,7 +745,8 @@ class ASTMatchFinder {
template <typename T>
bool matchesChildOf(const T &Node, const DynTypedMatcher &Matcher,
- BoundNodesTreeBuilder *Builder, BindKind Bind) {
+ BoundNodesTreeBuilder *Builder, BindKind Bind,
+ llvm::function_ref<bool(BoundNodesTreeBuilder*)> MatchCallback) {
static_assert(std::is_base_of<Decl, T>::value ||
std::is_base_of<Stmt, T>::value ||
std::is_base_of<NestedNameSpecifier, T>::value ||
@@ -752,6 +755,21 @@ class ASTMatchFinder {
std::is_base_of<QualType, T>::value ||
std::is_base_of<Attr, T>::value,
"unsupported type for recursive matching");
+ return matchesChildOf(DynTypedNode::create(Node), getASTContext(), Matcher,
+ Builder, Bind, MatchCallback);
+ }
+
+ template <typename T>
+ bool matchesChildOf(const T &Node, const DynTypedMatcher &Matcher,
+ BoundNodesTreeBuilder *Builder, BindKind Bind) {
+ static_assert(std::is_base_of<Decl, T>::value ||
+ std::is_base_of<Stmt, T>::value ||
+ std::is_base_of<NestedNameSpecifier, T>::value ||
+ std::is_base_of<NestedNameSpecifierLoc, T>::value ||
+ std::is_base_of<TypeLoc, T>::value ||
+ std::is_base_of<QualType, T>::value ||
+ std::is_base_of<Attr, T>::value,
+ "unsupported type for recursive matching");
return matchesChildOf(DynTypedNode::create(Node), getASTContext(), Matcher,
Builder, Bind);
}
@@ -795,6 +813,12 @@ class ASTMatchFinder {
bool isTraversalIgnoringImplicitNodes() const;
protected:
+ virtual bool matchesChildOf(const DynTypedNode &Node, ASTContext &Ctx,
+ const DynTypedMatcher &Matcher,
+ BoundNodesTreeBuilder *Builder,
+ BindKind Bind,
+ llvm::function_ref<bool(BoundNodesTreeBuilder *)> MatchCallback) = 0;
+
virtual bool matchesChildOf(const DynTypedNode &Node, ASTContext &Ctx,
const DynTypedMatcher &Matcher,
BoundNodesTreeBuilder *Builder,
@@ -809,6 +833,7 @@ class ASTMatchFinder {
const DynTypedMatcher &Matcher,
BoundNodesTreeBuilder *Builder,
AncestorMatchMode MatchMode) = 0;
+
private:
friend struct ASTChildrenNotSpelledInSourceScope;
virtual bool isMatchingChildrenNotSpelledInSource() const = 0;
@@ -2283,6 +2308,37 @@ using HasOpNameMatcher =
HasOpNameMatcher hasAnyOperatorNameFunc(ArrayRef<const StringRef *> NameRefs);
+/// Matches nodes of type T (CompoundStmt or StmtExpr) that contain sequences
+/// of consecutive substatements matching the provided matchers in order.
+///
+/// See \c forEachAdjacentSubstatements() in ASTMatchers.h for details.
+template <typename T, typename ArgT = std::vector<Matcher<Stmt>>>
+class ForEachAdjacentSubstatementsMatcher : public MatcherInterface<T> {
+ static_assert(std::is_same<T, CompoundStmt>::value ||
+ std::is_same<T, StmtExpr>::value,
+ "Matcher only supports `CompoundStmt` and `StmtExpr`");
+ static_assert(std::is_same<ArgT, std::vector<Matcher<Stmt>>>::value,
+ "Matcher ArgT must be std::vector<Matcher<Stmt>>");
+
+public:
+ explicit ForEachAdjacentSubstatementsMatcher(std::vector<Matcher<Stmt>> Matchers)
+ : Matchers(std::move(Matchers)) {}
+
+ bool matches(const T &Node, ASTMatchFinder *Finder,
+ BoundNodesTreeBuilder *Builder) const override;
+
+private:
+ std::vector<Matcher<Stmt>> Matchers;
+};
+
+using ForEachAdjacentSubstatementsMatcherType =
+ PolymorphicMatcher<ForEachAdjacentSubstatementsMatcher,
+ AST_POLYMORPHIC_SUPPORTED_TYPES(CompoundStmt, StmtExpr),
+ std::vector<Matcher<Stmt>>>;
+
+ForEachAdjacentSubstatementsMatcherType
+forEachAdjSubstatementsFunc(ArrayRef<const Matcher<Stmt> *> MatcherRefs);
+
using HasOverloadOpNameMatcher =
PolymorphicMatcher<HasOverloadedOperatorNameMatcher,
void(TypeList<CXXOperatorCallExpr, FunctionDecl>),
diff --git a/clang/lib/ASTMatchers/ASTMatchFinder.cpp b/clang/lib/ASTMatchers/ASTMatchFinder.cpp
index e8a0004c2e187..7739d45377416 100644
--- a/clang/lib/ASTMatchers/ASTMatchFinder.cpp
+++ b/clang/lib/ASTMatchers/ASTMatchFinder.cpp
@@ -593,14 +593,15 @@ class MatchASTVisitor : public RecursiveASTVisitor<MatchASTVisitor>,
return RecursiveASTVisitor<MatchASTVisitor>::dataTraverseNode(S, Queue);
}
- // Matches children or descendants of 'Node' with 'BaseMatcher'.
- bool memoizedMatchesRecursively(const DynTypedNode &Node, ASTContext &Ctx,
- const DynTypedMatcher &Matcher,
- BoundNodesTreeBuilder *Builder, int MaxDepth,
- BindKind Bind) {
+ // Matches children or descendants of 'Node' with a custom callback.
+ bool memoizedMatchesRecursively(
+ const DynTypedNode &Node, ASTContext &Ctx,
+ const DynTypedMatcher &Matcher, BoundNodesTreeBuilder *Builder,
+ int MaxDepth, BindKind Bind,
+ llvm::function_ref<bool(BoundNodesTreeBuilder *)> MatchCallback) {
// For AST-nodes that don't have an identity, we can't memoize.
if (!Node.getMemoizationData() || !Builder->isComparable())
- return matchesRecursively(Node, Matcher, Builder, MaxDepth, Bind);
+ return MatchCallback(Builder);
MatchKey Key;
Key.MatcherID = Matcher.getID();
@@ -618,8 +619,7 @@ class MatchASTVisitor : public RecursiveASTVisitor<MatchASTVisitor>,
MemoizedMatchResult Result;
Result.Nodes = *Builder;
- Result.ResultOfMatch =
- matchesRecursively(Node, Matcher, &Result.Nodes, MaxDepth, Bind);
+ Result.ResultOfMatch = MatchCallback(&Result.Nodes);
MemoizedMatchResult &CachedResult = ResultCache[Key];
CachedResult = std::move(Result);
@@ -668,13 +668,27 @@ class MatchASTVisitor : public RecursiveASTVisitor<MatchASTVisitor>,
bool Directly) override;
public:
- // Implements ASTMatchFinder::matchesChildOf.
+ // Implements ASTMatchFinder::matchesChildOf (with callback).
bool matchesChildOf(const DynTypedNode &Node, ASTContext &Ctx,
const DynTypedMatcher &Matcher,
- BoundNodesTreeBuilder *Builder, BindKind Bind) override {
+ BoundNodesTreeBuilder *Builder, BindKind Bind,
+ llvm::function_ref<bool(BoundNodesTreeBuilder *)>
+ MatchCallback) override {
if (ResultCache.size() > MaxMemoizationEntries)
ResultCache.clear();
- return memoizedMatchesRecursively(Node, Ctx, Matcher, Builder, 1, Bind);
+ return memoizedMatchesRecursively(Node, Ctx, Matcher, Builder, 1, Bind,
+ MatchCallback);
+ }
+
+ // Implements ASTMatchFinder::matchesChildOf (without callback).
+ bool matchesChildOf(const DynTypedNode &Node, ASTContext &Ctx,
+ const DynTypedMatcher &Matcher,
+ BoundNodesTreeBuilder *Builder, BindKind Bind) override {
+ return matchesChildOf(Node, Ctx, Matcher, Builder, Bind,
+ [this, &Node, &Matcher, Bind](
+ BoundNodesTreeBuilder* Nodes) -> bool {
+ return matchesRecursively(Node, Matcher, Nodes, 1, Bind);
+ });
}
// Implements ASTMatchFinder::matchesDescendantOf.
bool matchesDescendantOf(const DynTypedNode &Node, ASTContext &Ctx,
@@ -684,7 +698,11 @@ class MatchASTVisitor : public RecursiveASTVisitor<MatchASTVisitor>,
if (ResultCache.size() > MaxMemoizationEntries)
ResultCache.clear();
return memoizedMatchesRecursively(Node, Ctx, Matcher, Builder, INT_MAX,
- Bind);
+ Bind,
+ [this, &Node, &Matcher, Bind](
+ BoundNodesTreeBuilder* Nodes) -> bool {
+ return matchesRecursively(Node, Matcher, Nodes, INT_MAX, Bind);
+ });
}
// Implements ASTMatchFinder::matchesAncestorOf.
bool matchesAncestorOf(const DynTypedNode &Node, ASTContext &Ctx,
@@ -700,6 +718,7 @@ class MatchASTVisitor : public RecursiveASTVisitor<MatchASTVisitor>,
return matchesAnyAncestorOf(Node, Ctx, Matcher, Builder);
}
+
// Matches all registered matchers on the given node and calls the
// result callback for every node that matches.
void match(const DynTypedNode &Node) {
diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
index a556ffca96903..022d24738bc57 100644
--- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
+++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
@@ -129,7 +129,7 @@ class VariadicMatcher : public DynMatcherInterface {
class IdDynMatcher : public DynMatcherInterface {
public:
IdDynMatcher(StringRef ID,
- IntrusiveRefCntPtr<DynMatcherInterface> InnerMatcher)
+ IntrusiveRefCntPtr<const DynMatcherInterface> InnerMatcher)
: ID(ID), InnerMatcher(std::move(InnerMatcher)) {}
bool dynMatches(const DynTypedNode &DynNode, ASTMatchFinder *Finder,
@@ -145,7 +145,7 @@ class IdDynMatcher : public DynMatcherInterface {
private:
const std::string ID;
- const IntrusiveRefCntPtr<DynMatcherInterface> InnerMatcher;
+ const IntrusiveRefCntPtr<const DynMatcherInterface> InnerMatcher;
};
/// A matcher that always returns true.
@@ -167,7 +167,7 @@ class DynTraversalMatcherImpl : public DynMatcherInterface {
public:
explicit DynTraversalMatcherImpl(
clang::TraversalKind TK,
- IntrusiveRefCntPtr<DynMatcherInterface> InnerMatcher)
+ IntrusiveRefCntPtr<const DynMatcherInterface> InnerMatcher)
: TK(TK), InnerMatcher(std::move(InnerMatcher)) {}
bool dynMatches(const DynTypedNode &DynNode, ASTMatchFinder *Finder,
@@ -181,7 +181,7 @@ class DynTraversalMatcherImpl : public DynMatcherInterface {
private:
clang::TraversalKind TK;
- IntrusiveRefCntPtr<DynMatcherInterface> InnerMatcher;
+ IntrusiveRefCntPtr<const DynMatcherInterface> InnerMatcher;
};
} // namespace
@@ -467,6 +467,93 @@ hasAnyOverloadedOperatorNameFunc(ArrayRef<const StringRef *> NameRefs) {
return HasOverloadOpNameMatcher(vectorFromRefs(NameRefs));
}
+static std::vector<Matcher<Stmt>>
+vectorFromMatcherRefs(ArrayRef<const Matcher<Stmt> *> MatcherRefs) {
+ std::vector<Matcher<Stmt>> Matchers;
+ Matchers.reserve(MatcherRefs.size());
+ for (auto *Matcher : MatcherRefs)
+ Matchers.push_back(*Matcher);
+ return Matchers;
+}
+
+/// Creates a new BoundNodesTreeBuilder that extends the given builder.
+/// This is a helper to simplify the pattern of creating a new builder,
+/// adding the current builder's matches to it, and then using it for matching.
+static BoundNodesTreeBuilder extendBuilder(BoundNodesTreeBuilder &&Builder) {
+ BoundNodesTreeBuilder Extended;
+ Extended.addMatch(std::move(Builder));
+ return Extended;
+}
+
+ForEachAdjacentSubstatementsMatcherType
+forEachAdjSubstatementsFunc(ArrayRef<const Matcher<Stmt> *> MatcherRefs) {
+ return ForEachAdjacentSubstatementsMatcherType(vectorFromMatcherRefs(MatcherRefs));
+}
+
+template <typename T, typename ArgT>
+bool ForEachAdjacentSubstatementsMatcher<T, ArgT>::matches(
+ const T &Node, ASTMatchFinder *Finder,
+ BoundNodesTreeBuilder *Builder) const {
+ const CompoundStmt *CS = CompoundStmtMatcher<T>::get(Node);
+ if (!CS)
+ return false;
+
+ if (Matchers.empty())
+ return false;
+
+ // Create a DynTypedMatcher wrapper for this matcher instance to use for
+ // memoization. The matcher sequence is implicitly part of the matcher
+ // instance, so using 'this' pointer provides a unique identifier.
+ DynTypedMatcher MatcherWrapper(this);
+
+ // Use memoization to avoid re-running the same matcher on the same node.
+ return Finder->matchesChildOf(static_cast<const Stmt&>(*CS),
+ MatcherWrapper, Builder, ASTMatchFinder::BK_All,
+ [this, CS, Finder](BoundNodesTreeBuilder *MemoBuilder) -> bool {
+ // Search for all sequences of adjacent substatements that match the
+ // matchers
+ const auto BodyBegin = CS->body_begin();
+ const auto BodyEnd = CS->body_end();
+
+ bool FoundAny = false;
+
+ // Try each possible starting position
+ for (auto StartIt = BodyBegin; StartIt != BodyEnd; ++StartIt) {
+ // Check if there are enough statements remaining
+ if (std::distance(StartIt, BodyEnd) <
+ static_cast<ptrdiff_t>(Matchers.size()))
+ break;
+
+ // Start with a fresh builder for this sequence
+ BoundNodesTreeBuilder SequenceBuilder;
+
+ // Use enumerate to iterate over matchers and statements simultaneously
+ auto StmtRange =
+ llvm::make_range(StartIt, StartIt + Matchers.size());
+ for (auto [Idx...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/173587
More information about the cfe-commits
mailing list