Skip to content

Commit 2823e91

Browse files
AIR20AaronBallman
authored andcommitted
Add a new AST matcher 'optionally'.
This matcher matches any node and at the same time executes all its inner matchers to produce any possbile result bindings. This is useful when a user wants certain supplementary information that's not always present along with the main match result.
1 parent b675a76 commit 2823e91

File tree

6 files changed

+121
-12
lines changed

6 files changed

+121
-12
lines changed

clang/docs/LibASTMatchersReference.html

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4689,6 +4689,32 @@ <h2 id="traversal-matchers">AST Traversal Matchers</h2>
46894689
</pre></td></tr>
46904690

46914691

4692+
<tr><td>Matcher&lt;*&gt;</td><td class="name" onclick="toggle('optionally0')"><a name="optionally0Anchor">optionally</a></td><td>Matcher&lt;*&gt;, ..., Matcher&lt;*&gt;</td></tr>
4693+
<tr><td colspan="4" class="doc" id="optionally0"><pre>Matches any node regardless of the submatchers.
4694+
4695+
However, optionally will generate a result binding for each matching
4696+
submatcher.
4697+
4698+
Useful when additional information which may or may not present about a
4699+
main matching node is desired.
4700+
4701+
For example, in:
4702+
class Foo {
4703+
int bar;
4704+
}
4705+
The matcher:
4706+
cxxRecordDecl(
4707+
optionally(has(
4708+
fieldDecl(hasName("bar")).bind("var")
4709+
))).bind("record")
4710+
will produce a result binding for both "record" and "var".
4711+
The matcher will produce a "record" binding for even if there is no data
4712+
member named "bar" in that class.
4713+
4714+
Usable as: Any Matcher
4715+
</pre></td></tr>
4716+
4717+
46924718
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1AbstractConditionalOperator.html">AbstractConditionalOperator</a>&gt;</td><td class="name" onclick="toggle('hasCondition5')"><a name="hasCondition5Anchor">hasCondition</a></td><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Expr.html">Expr</a>&gt; InnerMatcher</td></tr>
46934719
<tr><td colspan="4" class="doc" id="hasCondition5"><pre>Matches the condition expression of an if statement, for loop,
46944720
switch statement or conditional operator.
@@ -5098,15 +5124,15 @@ <h2 id="traversal-matchers">AST Traversal Matchers</h2>
50985124
<tr><td colspan="4" class="doc" id="hasInitStatement2"><pre>Matches selection statements with initializer.
50995125

51005126
Given:
5101-
void foo() {
5127+
void foo() {
51025128
if (int i = foobar(); i &gt; 0) {}
51035129
switch (int i = foobar(); i) {}
5104-
for (auto&amp; a = get_range(); auto&amp; x : a) {}
5130+
for (auto&amp; a = get_range(); auto&amp; x : a) {}
51055131
}
5106-
void bar() {
5132+
void bar() {
51075133
if (foobar() &gt; 0) {}
51085134
switch (foobar()) {}
5109-
for (auto&amp; x : get_range()) {}
5135+
for (auto&amp; x : get_range()) {}
51105136
}
51115137
ifStmt(hasInitStatement(anything()))
51125138
matches the if statement in foo but not in bar.
@@ -6245,15 +6271,15 @@ <h2 id="traversal-matchers">AST Traversal Matchers</h2>
62456271
<tr><td colspan="4" class="doc" id="hasInitStatement0"><pre>Matches selection statements with initializer.
62466272

62476273
Given:
6248-
void foo() {
6274+
void foo() {
62496275
if (int i = foobar(); i &gt; 0) {}
62506276
switch (int i = foobar(); i) {}
6251-
for (auto&amp; a = get_range(); auto&amp; x : a) {}
6277+
for (auto&amp; a = get_range(); auto&amp; x : a) {}
62526278
}
6253-
void bar() {
6279+
void bar() {
62546280
if (foobar() &gt; 0) {}
62556281
switch (foobar()) {}
6256-
for (auto&amp; x : get_range()) {}
6282+
for (auto&amp; x : get_range()) {}
62576283
}
62586284
ifStmt(hasInitStatement(anything()))
62596285
matches the if statement in foo but not in bar.
@@ -7005,15 +7031,15 @@ <h2 id="traversal-matchers">AST Traversal Matchers</h2>
70057031
<tr><td colspan="4" class="doc" id="hasInitStatement1"><pre>Matches selection statements with initializer.
70067032

70077033
Given:
7008-
void foo() {
7034+
void foo() {
70097035
if (int i = foobar(); i &gt; 0) {}
70107036
switch (int i = foobar(); i) {}
7011-
for (auto&amp; a = get_range(); auto&amp; x : a) {}
7037+
for (auto&amp; a = get_range(); auto&amp; x : a) {}
70127038
}
7013-
void bar() {
7039+
void bar() {
70147040
if (foobar() &gt; 0) {}
70157041
switch (foobar()) {}
7016-
for (auto&amp; x : get_range()) {}
7042+
for (auto&amp; x : get_range()) {}
70177043
}
70187044
ifStmt(hasInitStatement(anything()))
70197045
matches the if statement in foo but not in bar.

clang/include/clang/ASTMatchers/ASTMatchers.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,6 +2540,36 @@ extern const internal::VariadicOperatorMatcherFunc<
25402540
2, std::numeric_limits<unsigned>::max()>
25412541
allOf;
25422542

2543+
/// Matches any node regardless of the submatchers.
2544+
///
2545+
/// However, \c optionally will generate a result binding for each matching
2546+
/// submatcher.
2547+
///
2548+
/// Useful when additional information which may or may not present about a
2549+
/// main matching node is desired.
2550+
///
2551+
/// For example, in:
2552+
/// \code
2553+
/// class Foo {
2554+
/// int bar;
2555+
/// }
2556+
/// \endcode
2557+
/// The matcher:
2558+
/// \code
2559+
/// cxxRecordDecl(
2560+
/// optionally(has(
2561+
/// fieldDecl(hasName("bar")).bind("var")
2562+
/// ))).bind("record")
2563+
/// \endcode
2564+
/// will produce a result binding for both "record" and "var".
2565+
/// The matcher will produce a "record" binding for even if there is no data
2566+
/// member named "bar" in that class.
2567+
///
2568+
/// Usable as: Any Matcher
2569+
extern const internal::VariadicOperatorMatcherFunc<
2570+
1, std::numeric_limits<unsigned>::max()>
2571+
optionally;
2572+
25432573
/// Matches sizeof (C99), alignof (C++11) and vec_step (OpenCL)
25442574
///
25452575
/// Given

clang/include/clang/ASTMatchers/ASTMatchersInternal.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,10 @@ class DynTypedMatcher {
363363
/// matches, but doesn't stop at the first match.
364364
VO_EachOf,
365365

366+
/// Matches any node but executes all inner matchers to find result
367+
/// bindings.
368+
VO_Optionally,
369+
366370
/// Matches nodes that do not match the provided matcher.
367371
///
368372
/// Uses the variadic matcher interface, but fails if

clang/lib/ASTMatchers/ASTMatchersInternal.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ bool AnyOfVariadicOperator(const ast_type_traits::DynTypedNode &DynNode,
6868
BoundNodesTreeBuilder *Builder,
6969
ArrayRef<DynTypedMatcher> InnerMatchers);
7070

71+
bool OptionallyVariadicOperator(const ast_type_traits::DynTypedNode &DynNode,
72+
ASTMatchFinder *Finder,
73+
BoundNodesTreeBuilder *Builder,
74+
ArrayRef<DynTypedMatcher> InnerMatchers);
75+
7176
void BoundNodesTreeBuilder::visitMatches(Visitor *ResultVisitor) {
7277
if (Bindings.empty())
7378
Bindings.push_back(BoundNodesMap());
@@ -184,6 +189,11 @@ DynTypedMatcher DynTypedMatcher::constructVariadic(
184189
SupportedKind, RestrictKind,
185190
new VariadicMatcher<EachOfVariadicOperator>(std::move(InnerMatchers)));
186191

192+
case VO_Optionally:
193+
return DynTypedMatcher(SupportedKind, RestrictKind,
194+
new VariadicMatcher<OptionallyVariadicOperator>(
195+
std::move(InnerMatchers)));
196+
187197
case VO_UnaryNot:
188198
// FIXME: Implement the Not operator to take a single matcher instead of a
189199
// vector.
@@ -347,6 +357,20 @@ bool AnyOfVariadicOperator(const ast_type_traits::DynTypedNode &DynNode,
347357
return false;
348358
}
349359

360+
bool OptionallyVariadicOperator(const ast_type_traits::DynTypedNode &DynNode,
361+
ASTMatchFinder *Finder,
362+
BoundNodesTreeBuilder *Builder,
363+
ArrayRef<DynTypedMatcher> InnerMatchers) {
364+
BoundNodesTreeBuilder Result;
365+
for (const DynTypedMatcher &InnerMatcher : InnerMatchers) {
366+
BoundNodesTreeBuilder BuilderInner(*Builder);
367+
if (InnerMatcher.matches(DynNode, Finder, &BuilderInner))
368+
Result.addMatch(BuilderInner);
369+
}
370+
*Builder = std::move(Result);
371+
return true;
372+
}
373+
350374
inline static
351375
std::vector<std::string> vectorFromRefs(ArrayRef<const StringRef *> NameRefs) {
352376
std::vector<std::string> Names;
@@ -797,6 +821,9 @@ const internal::VariadicOperatorMatcherFunc<
797821
const internal::VariadicOperatorMatcherFunc<
798822
2, std::numeric_limits<unsigned>::max()>
799823
allOf = {internal::DynTypedMatcher::VO_AllOf};
824+
const internal::VariadicOperatorMatcherFunc<
825+
1, std::numeric_limits<unsigned>::max()>
826+
optionally = {internal::DynTypedMatcher::VO_Optionally};
800827
const internal::VariadicFunction<internal::Matcher<NamedDecl>, StringRef,
801828
internal::hasAnyNameFunc>
802829
hasAnyName = {};

clang/lib/ASTMatchers/Dynamic/Registry.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ RegistryMaps::RegistryMaps() {
456456
REGISTER_MATCHER(on);
457457
REGISTER_MATCHER(onImplicitObjectArgument);
458458
REGISTER_MATCHER(opaqueValueExpr);
459+
REGISTER_MATCHER(optionally);
459460
REGISTER_MATCHER(parameterCountIs);
460461
REGISTER_MATCHER(parenExpr);
461462
REGISTER_MATCHER(parenListExpr);

clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1866,6 +1866,27 @@ TEST(EachOf, BehavesLikeAnyOfUnlessBothMatch) {
18661866
has(fieldDecl(hasName("b")).bind("v"))))));
18671867
}
18681868

1869+
TEST(Optionally, SubmatchersDoNotMatch) {
1870+
EXPECT_TRUE(matchAndVerifyResultFalse(
1871+
"class A { int a; int b; };",
1872+
recordDecl(optionally(has(fieldDecl(hasName("c")).bind("v")),
1873+
has(fieldDecl(hasName("d")).bind("v")))),
1874+
std::make_unique<VerifyIdIsBoundTo<FieldDecl>>("v")));
1875+
}
1876+
1877+
TEST(Optionally, SubmatchersMatch) {
1878+
EXPECT_TRUE(matchAndVerifyResultTrue(
1879+
"class A { int a; int c; };",
1880+
recordDecl(optionally(has(fieldDecl(hasName("a")).bind("v")),
1881+
has(fieldDecl(hasName("b")).bind("v")))),
1882+
std::make_unique<VerifyIdIsBoundTo<FieldDecl>>("v", 1)));
1883+
EXPECT_TRUE(matchAndVerifyResultTrue(
1884+
"class A { int c; int b; };",
1885+
recordDecl(optionally(has(fieldDecl(hasName("c")).bind("v")),
1886+
has(fieldDecl(hasName("b")).bind("v")))),
1887+
std::make_unique<VerifyIdIsBoundTo<FieldDecl>>("v", 2)));
1888+
}
1889+
18691890
TEST(IsTemplateInstantiation, MatchesImplicitClassTemplateInstantiation) {
18701891
// Make sure that we can both match the class by name (::X) and by the type
18711892
// the template was instantiated with (via a field).

0 commit comments

Comments
 (0)