[clang] [llvm] [clang] Add hasAdjSubstatements matcher (PR #169965)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Nov 28 15:45:11 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-adt
Author: Denis Mikhailov (denzor200)
<details>
<summary>Changes</summary>
I need this mather 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
---
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
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.
``````````
</details>
https://github.com/llvm/llvm-project/pull/169965
More information about the llvm-commits
mailing list