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.
This commit is contained in:
Rihan Yang 2020-01-08 14:09:29 -05:00 committed by Aaron Ballman
parent b675a7628c
commit 2823e91d55
6 changed files with 121 additions and 12 deletions

View File

@ -4689,6 +4689,32 @@ Usable as: Any Matcher
</pre></td></tr>
<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>
<tr><td colspan="4" class="doc" id="optionally0"><pre>Matches any node regardless of the submatchers.
However, optionally will generate a result binding for each matching
submatcher.
Useful when additional information which may or may not present about a
main matching node is desired.
For example, in:
class Foo {
int bar;
}
The matcher:
cxxRecordDecl(
optionally(has(
fieldDecl(hasName("bar")).bind("var")
))).bind("record")
will produce a result binding for both "record" and "var".
The matcher will produce a "record" binding for even if there is no data
member named "bar" in that class.
Usable as: Any Matcher
</pre></td></tr>
<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>
<tr><td colspan="4" class="doc" id="hasCondition5"><pre>Matches the condition expression of an if statement, for loop,
switch statement or conditional operator.
@ -5098,15 +5124,15 @@ with compoundStmt()
<tr><td colspan="4" class="doc" id="hasInitStatement2"><pre>Matches selection statements with initializer.
Given:
void foo() {
void foo() {
if (int i = foobar(); i &gt; 0) {}
switch (int i = foobar(); i) {}
for (auto&amp; a = get_range(); auto&amp; x : a) {}
for (auto&amp; a = get_range(); auto&amp; x : a) {}
}
void bar() {
void bar() {
if (foobar() &gt; 0) {}
switch (foobar()) {}
for (auto&amp; x : get_range()) {}
for (auto&amp; x : get_range()) {}
}
ifStmt(hasInitStatement(anything()))
matches the if statement in foo but not in bar.
@ -6245,15 +6271,15 @@ Examples matches the if statement
<tr><td colspan="4" class="doc" id="hasInitStatement0"><pre>Matches selection statements with initializer.
Given:
void foo() {
void foo() {
if (int i = foobar(); i &gt; 0) {}
switch (int i = foobar(); i) {}
for (auto&amp; a = get_range(); auto&amp; x : a) {}
for (auto&amp; a = get_range(); auto&amp; x : a) {}
}
void bar() {
void bar() {
if (foobar() &gt; 0) {}
switch (foobar()) {}
for (auto&amp; x : get_range()) {}
for (auto&amp; x : get_range()) {}
}
ifStmt(hasInitStatement(anything()))
matches the if statement in foo but not in bar.
@ -7005,15 +7031,15 @@ Example matches true (matcher = hasCondition(cxxBoolLiteral(equals(true))))
<tr><td colspan="4" class="doc" id="hasInitStatement1"><pre>Matches selection statements with initializer.
Given:
void foo() {
void foo() {
if (int i = foobar(); i &gt; 0) {}
switch (int i = foobar(); i) {}
for (auto&amp; a = get_range(); auto&amp; x : a) {}
for (auto&amp; a = get_range(); auto&amp; x : a) {}
}
void bar() {
void bar() {
if (foobar() &gt; 0) {}
switch (foobar()) {}
for (auto&amp; x : get_range()) {}
for (auto&amp; x : get_range()) {}
}
ifStmt(hasInitStatement(anything()))
matches the if statement in foo but not in bar.

View File

@ -2540,6 +2540,36 @@ extern const internal::VariadicOperatorMatcherFunc<
2, std::numeric_limits<unsigned>::max()>
allOf;
/// Matches any node regardless of the submatchers.
///
/// However, \c optionally will generate a result binding for each matching
/// submatcher.
///
/// Useful when additional information which may or may not present about a
/// main matching node is desired.
///
/// For example, in:
/// \code
/// class Foo {
/// int bar;
/// }
/// \endcode
/// The matcher:
/// \code
/// cxxRecordDecl(
/// optionally(has(
/// fieldDecl(hasName("bar")).bind("var")
/// ))).bind("record")
/// \endcode
/// will produce a result binding for both "record" and "var".
/// The matcher will produce a "record" binding for even if there is no data
/// member named "bar" in that class.
///
/// Usable as: Any Matcher
extern const internal::VariadicOperatorMatcherFunc<
1, std::numeric_limits<unsigned>::max()>
optionally;
/// Matches sizeof (C99), alignof (C++11) and vec_step (OpenCL)
///
/// Given

View File

@ -363,6 +363,10 @@ public:
/// matches, but doesn't stop at the first match.
VO_EachOf,
/// Matches any node but executes all inner matchers to find result
/// bindings.
VO_Optionally,
/// Matches nodes that do not match the provided matcher.
///
/// Uses the variadic matcher interface, but fails if

View File

@ -68,6 +68,11 @@ bool AnyOfVariadicOperator(const ast_type_traits::DynTypedNode &DynNode,
BoundNodesTreeBuilder *Builder,
ArrayRef<DynTypedMatcher> InnerMatchers);
bool OptionallyVariadicOperator(const ast_type_traits::DynTypedNode &DynNode,
ASTMatchFinder *Finder,
BoundNodesTreeBuilder *Builder,
ArrayRef<DynTypedMatcher> InnerMatchers);
void BoundNodesTreeBuilder::visitMatches(Visitor *ResultVisitor) {
if (Bindings.empty())
Bindings.push_back(BoundNodesMap());
@ -184,6 +189,11 @@ DynTypedMatcher DynTypedMatcher::constructVariadic(
SupportedKind, RestrictKind,
new VariadicMatcher<EachOfVariadicOperator>(std::move(InnerMatchers)));
case VO_Optionally:
return DynTypedMatcher(SupportedKind, RestrictKind,
new VariadicMatcher<OptionallyVariadicOperator>(
std::move(InnerMatchers)));
case VO_UnaryNot:
// FIXME: Implement the Not operator to take a single matcher instead of a
// vector.
@ -347,6 +357,20 @@ bool AnyOfVariadicOperator(const ast_type_traits::DynTypedNode &DynNode,
return false;
}
bool OptionallyVariadicOperator(const ast_type_traits::DynTypedNode &DynNode,
ASTMatchFinder *Finder,
BoundNodesTreeBuilder *Builder,
ArrayRef<DynTypedMatcher> InnerMatchers) {
BoundNodesTreeBuilder Result;
for (const DynTypedMatcher &InnerMatcher : InnerMatchers) {
BoundNodesTreeBuilder BuilderInner(*Builder);
if (InnerMatcher.matches(DynNode, Finder, &BuilderInner))
Result.addMatch(BuilderInner);
}
*Builder = std::move(Result);
return true;
}
inline static
std::vector<std::string> vectorFromRefs(ArrayRef<const StringRef *> NameRefs) {
std::vector<std::string> Names;
@ -797,6 +821,9 @@ const internal::VariadicOperatorMatcherFunc<
const internal::VariadicOperatorMatcherFunc<
2, std::numeric_limits<unsigned>::max()>
allOf = {internal::DynTypedMatcher::VO_AllOf};
const internal::VariadicOperatorMatcherFunc<
1, std::numeric_limits<unsigned>::max()>
optionally = {internal::DynTypedMatcher::VO_Optionally};
const internal::VariadicFunction<internal::Matcher<NamedDecl>, StringRef,
internal::hasAnyNameFunc>
hasAnyName = {};

View File

@ -456,6 +456,7 @@ RegistryMaps::RegistryMaps() {
REGISTER_MATCHER(on);
REGISTER_MATCHER(onImplicitObjectArgument);
REGISTER_MATCHER(opaqueValueExpr);
REGISTER_MATCHER(optionally);
REGISTER_MATCHER(parameterCountIs);
REGISTER_MATCHER(parenExpr);
REGISTER_MATCHER(parenListExpr);

View File

@ -1866,6 +1866,27 @@ TEST(EachOf, BehavesLikeAnyOfUnlessBothMatch) {
has(fieldDecl(hasName("b")).bind("v"))))));
}
TEST(Optionally, SubmatchersDoNotMatch) {
EXPECT_TRUE(matchAndVerifyResultFalse(
"class A { int a; int b; };",
recordDecl(optionally(has(fieldDecl(hasName("c")).bind("v")),
has(fieldDecl(hasName("d")).bind("v")))),
std::make_unique<VerifyIdIsBoundTo<FieldDecl>>("v")));
}
TEST(Optionally, SubmatchersMatch) {
EXPECT_TRUE(matchAndVerifyResultTrue(
"class A { int a; int c; };",
recordDecl(optionally(has(fieldDecl(hasName("a")).bind("v")),
has(fieldDecl(hasName("b")).bind("v")))),
std::make_unique<VerifyIdIsBoundTo<FieldDecl>>("v", 1)));
EXPECT_TRUE(matchAndVerifyResultTrue(
"class A { int c; int b; };",
recordDecl(optionally(has(fieldDecl(hasName("c")).bind("v")),
has(fieldDecl(hasName("b")).bind("v")))),
std::make_unique<VerifyIdIsBoundTo<FieldDecl>>("v", 2)));
}
TEST(IsTemplateInstantiation, MatchesImplicitClassTemplateInstantiation) {
// Make sure that we can both match the class by name (::X) and by the type
// the template was instantiated with (via a field).