Skip to content

Conversation

@denzor200
Copy link

I need this mather at least to implement #133110 and #38471
Also maybe we will use it in #158462

@github-actions
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot
Copy link
Member

llvmbot commented Nov 28, 2025

@llvm/pr-subscribers-llvm-adt

Author: Denis Mikhailov (denzor200)

Changes

I need this mather at least to implement #133110 and #38471
Also maybe we will use it in #158462


Full diff: https://github.com/llvm/llvm-project/pull/169965.diff

6 Files Affected:

  • (modified) clang/include/clang/ASTMatchers/ASTMatchers.h (+25-1)
  • (modified) clang/include/clang/ASTMatchers/ASTMatchersInternal.h (+31)
  • (modified) clang/lib/ASTMatchers/ASTMatchersInternal.cpp (+57)
  • (modified) clang/lib/ASTMatchers/Dynamic/Registry.cpp (+1)
  • (modified) clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp (+200)
  • (modified) llvm/include/llvm/ADT/STLExtras.h (+5)
diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h
index bca2d8425b3f5..87d6bd7e1b9a3 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchers.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchers.h
@@ -95,6 +95,7 @@
 #include <limits>
 #include <optional>
 #include <string>
+#include <tuple>
 #include <utility>
 #include <vector>
 
@@ -5889,7 +5890,6 @@ AST_MATCHER_P(FunctionDecl, hasAnyBody,
           InnerMatcher.matches(*Statement, Finder, Builder));
 }
 
-
 /// Matches compound statements where at least one substatement matches
 /// a given matcher. Also matches StmtExprs that have CompoundStmt as children.
 ///
@@ -5911,6 +5911,30 @@ AST_POLYMORPHIC_MATCHER_P(hasAnySubstatement,
                                           Builder) != CS->body_end();
 }
 
+/// Matches compound statements that contain adjacent substatements matching
+/// the provided sequence of matchers. Also matches StmtExprs that have
+/// CompoundStmt as children.
+///
+/// Given
+/// \code
+///   { {}; 1+2; }
+/// \endcode
+/// hasAdjSubstatements(compoundStmt(), binaryOperator())
+///   matches '{ {}; 1+2; }'
+/// with compoundStmt()
+///   matching '{}'
+/// with binaryOperator()
+///   matching '1+2'
+///
+///    hasAdjSubstatements(compoundStmt(), binaryOperator(), returnStmt())
+///  Is equivalent to matching a compound statement that contains
+///  a compound statement immediately followed by a binary operator
+///  immediately followed by a return statement.
+extern const internal::VariadicFunction<
+    internal::HasAdjSubstatementsMatcherType,
+    internal::Matcher<Stmt>, internal::hasAdjSubstatementsFunc>
+    hasAdjSubstatements;
+
 /// 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..5fb63af28dc1b 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
@@ -2283,6 +2283,37 @@ using HasOpNameMatcher =
 
 HasOpNameMatcher hasAnyOperatorNameFunc(ArrayRef<const StringRef *> NameRefs);
 
+/// Matches nodes of type T (CompoundStmt or StmtExpr) that contain a sequence
+/// of consecutive substatements matching the provided matchers in order.
+///
+/// See \c hasAdjSubstatements() in ASTMatchers.h for details.
+template <typename T, typename ArgT = std::vector<Matcher<Stmt>>>
+class HasAdjSubstatementsMatcher : 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 HasAdjSubstatementsMatcher(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 HasAdjSubstatementsMatcherType =
+    PolymorphicMatcher<HasAdjSubstatementsMatcher,
+                       void(TypeList<CompoundStmt, StmtExpr>),
+                       std::vector<Matcher<Stmt>>>;
+
+HasAdjSubstatementsMatcherType
+hasAdjSubstatementsFunc(ArrayRef<const Matcher<Stmt> *> MatcherRefs);
+
 using HasOverloadOpNameMatcher =
     PolymorphicMatcher<HasOverloadedOperatorNameMatcher,
                        void(TypeList<CXXOperatorCallExpr, FunctionDecl>),
diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
index 0874b3d0c45f5..14ff5d919eae2 100644
--- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
+++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
@@ -31,6 +31,7 @@
 #include "llvm/Support/Regex.h"
 #include "llvm/Support/WithColor.h"
 #include "llvm/Support/raw_ostream.h"
+#include <algorithm>
 #include <cassert>
 #include <cstddef>
 #include <optional>
@@ -467,6 +468,58 @@ 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;
+}
+
+HasAdjSubstatementsMatcherType
+hasAdjSubstatementsFunc(ArrayRef<const Matcher<Stmt> *> MatcherRefs) {
+  return HasAdjSubstatementsMatcherType(vectorFromMatcherRefs(MatcherRefs));
+}
+
+template <typename T, typename ArgT>
+bool HasAdjSubstatementsMatcher<T, ArgT>::matches(
+    const T &Node, ASTMatchFinder *Finder,
+    BoundNodesTreeBuilder *Builder) const {
+  const CompoundStmt *CS = CompoundStmtMatcher<T>::get(Node);
+  if (!CS)
+    return false;
+
+  // Use llvm::search with lambda predicate that matches statements against
+  // matchers and accumulates BoundNodesTreeBuilder state
+  BoundNodesTreeBuilder CurrentBuilder;
+  const auto Found = llvm::search(
+      CS->body(), Matchers,
+      [&](const Stmt *StmtPtr, const Matcher<Stmt> &Matcher) mutable {
+        BoundNodesTreeBuilder StepBuilder;
+        StepBuilder.addMatch(CurrentBuilder);
+        if (!Matcher.matches(*StmtPtr, Finder, &StepBuilder)) {
+          // reset the state
+          CurrentBuilder = {};
+          return false;
+        }
+        // Invalidate the state
+        CurrentBuilder = StepBuilder;
+        return true;
+      });
+
+  if (Found == CS->body_end())
+    return false;
+
+  Builder->addMatch(CurrentBuilder);
+  return true;
+}
+
+template bool HasAdjSubstatementsMatcher<CompoundStmt>::matches(
+    const CompoundStmt &, ASTMatchFinder *, BoundNodesTreeBuilder *) const;
+template bool HasAdjSubstatementsMatcher<StmtExpr>::matches(
+    const StmtExpr &, ASTMatchFinder *, BoundNodesTreeBuilder *) const;
+
 HasNameMatcher::HasNameMatcher(std::vector<std::string> N)
     : UseUnqualifiedMatch(
           llvm::all_of(N, [](StringRef Name) { return !Name.contains("::"); })),
@@ -1046,6 +1099,10 @@ const internal::VariadicFunction<internal::Matcher<NamedDecl>, StringRef,
 const internal::VariadicFunction<internal::HasOpNameMatcher, StringRef,
                                  internal::hasAnyOperatorNameFunc>
     hasAnyOperatorName = {};
+const internal::VariadicFunction<internal::HasAdjSubstatementsMatcherType,
+                                 internal::Matcher<Stmt>,
+                                 internal::hasAdjSubstatementsFunc>
+    hasAdjSubstatements = {};
 const internal::VariadicFunction<internal::HasOverloadOpNameMatcher, StringRef,
                                  internal::hasAnyOverloadedOperatorNameFunc>
     hasAnyOverloadedOperatorName = {};
diff --git a/clang/lib/ASTMatchers/Dynamic/Registry.cpp b/clang/lib/ASTMatchers/Dynamic/Registry.cpp
index 66848f7c42127..7b21a60c5f189 100644
--- a/clang/lib/ASTMatchers/Dynamic/Registry.cpp
+++ b/clang/lib/ASTMatchers/Dynamic/Registry.cpp
@@ -288,6 +288,7 @@ RegistryMaps::RegistryMaps() {
   REGISTER_MATCHER(hasAnyPlacementArg);
   REGISTER_MATCHER(hasAnySelector);
   REGISTER_MATCHER(hasAnySubstatement);
+  REGISTER_MATCHER(hasAdjSubstatements);
   REGISTER_MATCHER(hasAnyTemplateArgument);
   REGISTER_MATCHER(hasAnyTemplateArgumentLoc);
   REGISTER_MATCHER(hasAnyUsingShadowDecl);
diff --git a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
index c0a03deb5b543..1c86b16037d0f 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
@@ -2399,6 +2399,206 @@ TEST(HasAnySubstatement, FindsSubstatementBetweenOthers) {
                       compoundStmt(hasAnySubstatement(forStmt()))));
 }
 
+TEST(HasAdjSubstatements, MatchesAdjacentSubstatements) {
+  // Basic case: compound statement followed by binary operator
+  EXPECT_TRUE(matches("void f() { {} 1+2; }",
+                      compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                        binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, DoesNotMatchNonAdjacentSubstatements) {
+  // Statements exist but not adjacent
+  EXPECT_TRUE(notMatches("void f() { {} 1; 1+2; }",
+                         compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                          binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, MatchesInNestedCompoundStatements) {
+  // Should match in nested compound statements
+  EXPECT_TRUE(matches("void f() { if (true) { {} 1+2; } }",
+                      compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                      binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, MatchesFirstAdjacentPair) {
+  // When multiple adjacent pairs exist, should match the first one
+  EXPECT_TRUE(matches("void f() { {} 1+2; {} 3+4; }",
+                      compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                      binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, DoesNotMatchEmptyCompound) {
+  // Empty compound statement has no adjacent pairs
+  EXPECT_TRUE(notMatches("void f() { }",
+                         compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                         binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, DoesNotMatchSingleStatement) {
+  // Single statement has no adjacent pairs
+  EXPECT_TRUE(notMatches("void f() { 1+2; }",
+                         compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                        binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, MatchesDifferentStatementTypes) {
+  // Test with different statement types
+  EXPECT_TRUE(matches("void f() { for (;;); while (true); }",
+                      compoundStmt(hasAdjSubstatements(forStmt(), whileStmt()))));
+  
+  EXPECT_TRUE(matches("void f() { int x; return; }",
+                      compoundStmt(hasAdjSubstatements(declStmt(), returnStmt()))));
+}
+
+TEST(HasAdjSubstatements, WorksWithStmtExpr) {
+  // Test that it works with StmtExpr (polymorphic support)
+  EXPECT_TRUE(matches("void f() { int x = ({ {} 1+2; }); }",
+                      stmtExpr(hasAdjSubstatements(compoundStmt(),
+                                                   binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, DoesNotMatchWrongOrder) {
+  // The order matters - binaryOperator must come after compoundStmt
+  EXPECT_TRUE(notMatches("void f() { 1+2; {} }",
+                         compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                         binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, MatchesWithStatementsBetween) {
+  // Should still match even if there are other statements before/after
+  EXPECT_TRUE(matches("void f() { int x; {} 1+2; int y; }",
+                      compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                     binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicMatchesThreeAdjacentSubstatements) {
+  // Test variadic version with 3 matchers
+  EXPECT_TRUE(matches("void f() { {} 1+2; 3+4; }",
+                      compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                       binaryOperator(),
+                                                       binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicMatchesFourAdjacentSubstatements) {
+  // Test variadic version with 4 matchers
+  EXPECT_TRUE(matches("void f() { int x; return; {} 1+2; }",
+                      compoundStmt(hasAdjSubstatements(declStmt(),
+                                                       returnStmt(),
+                                                       compoundStmt(),
+                                                       binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicMatchesFiveAdjacentSubstatements) {
+  // Test variadic version with 5 matchers
+  EXPECT_TRUE(matches("void f() { for (;;); while (true); if (true) {} return; 1+2; }",
+                      compoundStmt(hasAdjSubstatements(forStmt(),
+                                                       whileStmt(),
+                                                       ifStmt(),
+                                                       returnStmt(),
+                                                       binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicDoesNotMatchNonAdjacentSequence) {
+  // Three matchers but statements are not all adjacent
+  EXPECT_TRUE(notMatches("void f() { {} 1; 1+2; 3+4; }",
+                         compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                         binaryOperator(),
+                                                         binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicDoesNotMatchPartialSequence) {
+  // First two match but third doesn't
+  EXPECT_TRUE(notMatches("void f() { {} 1+2; return; }",
+                         compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                         binaryOperator(),
+                                                         binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicMatchesInNestedCompound) {
+  // Test variadic version in nested compound statements
+  EXPECT_TRUE(matches("void f() { if (true) { {} 1+2; 3+4; } }",
+                      compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                      binaryOperator(),
+                                                      binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicMatchesWithDifferentTypes) {
+  // Test variadic version with different statement types
+  EXPECT_TRUE(matches("void f() { for (;;); while (true); if (true) {} }",
+                      compoundStmt(hasAdjSubstatements(forStmt(),
+                                                       whileStmt(),
+                                                       ifStmt()))));
+}
+
+TEST(HasAdjSubstatements, VariadicDoesNotMatchWrongOrder) {
+  // Order matters in variadic version
+  EXPECT_TRUE(notMatches("void f() { 1+2; {} 3+4; }",
+                         compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                          binaryOperator(),
+                                                          binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicMatchesFirstSequence) {
+  // When multiple sequences exist, should match the first one
+  EXPECT_TRUE(matches("void f() { {} 1+2; 3+4; {} 5+6; 7+8; }",
+                      compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                      binaryOperator(),
+                                                      binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicWorksWithStmtExpr) {
+  // Test variadic version with StmtExpr
+  EXPECT_TRUE(matches("void f() { int x = ({ {} 1+2; 3+4; }); }",
+                      stmtExpr(hasAdjSubstatements(compoundStmt(),
+                                                   binaryOperator(),
+                                                   binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicRequiresMinimumStatements) {
+  // Need at least as many statements as matchers
+  EXPECT_TRUE(notMatches("void f() { {} 1+2; }",
+                         compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                          binaryOperator(),
+                                                          binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicMatchesWithStatementsBetween) {
+  // Should still match even if there are other statements before/after
+  EXPECT_TRUE(matches("void f() { int x; {} 1+2; 3+4; int y; }",
+                      compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                       binaryOperator(),
+                                                       binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicMatchesComplexSequence) {
+  // Test with a complex sequence of different statement types
+  EXPECT_TRUE(matches("void f() { int a; int b; return; {} 1+2; }",
+                      compoundStmt(hasAdjSubstatements(declStmt(),
+                                                       declStmt(),
+                                                       returnStmt(),
+                                                       compoundStmt(),
+                                                       binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicDoesNotMatchGapInSequence) {
+  // Sequence has a gap in the middle
+  EXPECT_TRUE(notMatches("void f() { {} 1+2; int x; 3+4; }",
+                         compoundStmt(hasAdjSubstatements(compoundStmt(),
+                                                         binaryOperator(),
+                                                         binaryOperator()))));
+}
+
+TEST(HasAdjSubstatements, VariadicMatchesLongSequence) {
+  // Test with a longer sequence (6 statements)
+  EXPECT_TRUE(matches("void f() { int a; int b; int c; return; {} 1+2; }",
+                      compoundStmt(hasAdjSubstatements(declStmt(),
+                                                       declStmt(),
+                                                       declStmt(),
+                                                       returnStmt(),
+                                                       compoundStmt(),
+                                                       binaryOperator()))));
+}
+
 TEST(Member, MatchesMemberAllocationFunction) {
   // Fails in C++11 mode
   EXPECT_TRUE(matchesConditionally(
diff --git a/llvm/include/llvm/ADT/STLExtras.h b/llvm/include/llvm/ADT/STLExtras.h
index af0e4a36be1b1..9fc8095389860 100644
--- a/llvm/include/llvm/ADT/STLExtras.h
+++ b/llvm/include/llvm/ADT/STLExtras.h
@@ -1778,6 +1778,11 @@ OutputIt copy_if(R &&Range, OutputIt Out, UnaryPredicate P) {
   return std::copy_if(adl_begin(Range), adl_end(Range), Out, P);
 }
 
+template <typename R1, typename R2, typename BinaryPredicate>
+auto search(R1 &&Range1, R2 &&Range2, BinaryPredicate P) {
+  return std::search(adl_begin(Range1), adl_end(Range1), adl_begin(Range2), adl_end(Range2), P);
+}
+
 /// Return the single value in \p Range that satisfies
 /// \p P(<member of \p Range> *, AllowRepeats)->T * returning nullptr
 /// when no values or multiple values were found.

@github-actions
Copy link

github-actions bot commented Nov 28, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@zwuis zwuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please run "clang/docs/tools/dump_ast_matchers.py" to update documentation.

Please add a release note entry to "clang/docs/ReleaseNotes.rst".

#include <limits>
#include <optional>
#include <string>
#include <tuple>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated changes?

#include "llvm/Support/Regex.h"
#include "llvm/Support/WithColor.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be llvm/ADT/STLExtras.h?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests to "llvm/unittests/ADT/STLExtrasTest.cpp".

It seems that we should add this function in a separate PR.

Comment on lines +2292 to +2294
static_assert(std::is_same<T, CompoundStmt>::value ||
std::is_same<T, StmtExpr>::value,
"Matcher only supports `CompoundStmt` and `StmtExpr`");
Copy link
Contributor

@zwuis zwuis Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we refer to AST_POLYMORPHIC_MATCHER to take CompoundStmt or StmtExpr instead of static_assert?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants