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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
