Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion clang/include/clang/ASTMatchers/ASTMatchers.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
#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 <utility>
#include <vector>

Expand Down Expand Up @@ -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.
///
Expand All @@ -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.
///
Expand Down
31 changes: 31 additions & 0 deletions clang/include/clang/ASTMatchers/ASTMatchersInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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`");
Comment on lines +2292 to +2294
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?

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>),
Expand Down
57 changes: 57 additions & 0 deletions clang/lib/ASTMatchers/ASTMatchersInternal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#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?

#include <cassert>
#include <cstddef>
#include <optional>
Expand Down Expand Up @@ -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("::"); })),
Expand Down Expand Up @@ -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 = {};
Expand Down
1 change: 1 addition & 0 deletions clang/lib/ASTMatchers/Dynamic/Registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
193 changes: 193 additions & 0 deletions clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2399,6 +2399,199 @@ 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(
Expand Down
6 changes: 6 additions & 0 deletions llvm/include/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.

Original file line number Diff line number Diff line change
Expand Up @@ -1778,6 +1778,12 @@ 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.
Expand Down