diff --git a/clang/docs/LibASTMatchersReference.html b/clang/docs/LibASTMatchersReference.html --- a/clang/docs/LibASTMatchersReference.html +++ b/clang/docs/LibASTMatchersReference.html @@ -2593,6 +2593,29 @@ +unspecifiedmapAnyOfnodeMatcherFunction... +
Matches any of the NodeMatchers with InnerMatchers nested within
+
+Given
+  if (true);
+  for (; true; );
+with the matcher
+  mapAnyOf(ifStmt, forStmt).with(
+    hasCondition(cxxBoolLiteralExpr(equals(true)))
+    ).bind("trueCond")
+matches the if and the for. It is equivalent to:
+  auto trueCond = hasCondition(cxxBoolLiteralExpr(equals(true)));
+  anyOf(
+    ifStmt(trueCond).bind("trueCond"),
+    forStmt(trueCond).bind("trueCond")
+    );
+
+The with() chain-call accepts zero or more matchers which are combined
+as-if with allOf() in each of the node matchers.
+Usable as: Any Matcher
+
+ + Matcher<*>unlessMatcher<*>
Matches if the provided matcher does not match.
 
@@ -2980,7 +3003,7 @@
 
 
 Matcher<CXXDependentScopeMemberExpr>hasMemberNamestd::string N
-
Matches template-dependent, but known, member names
+
Matches template-dependent, but known, member names.
 
 In template declarations, dependent members are not resolved and so can
 not be matched to particular named declarations.
diff --git a/clang/docs/tools/dump_ast_matchers.py b/clang/docs/tools/dump_ast_matchers.py
--- a/clang/docs/tools/dump_ast_matchers.py
+++ b/clang/docs/tools/dump_ast_matchers.py
@@ -119,8 +119,15 @@
   ids[name] += 1
   args = unify_arguments(args)
   result_type = unify_type(result_type)
+
+  docs_result_type = esc('Matcher<%s>' % result_type);
+
+  if name == 'mapAnyOf':
+    args = "nodeMatcherFunction..."
+    docs_result_type = "unspecified"
+
   matcher_html = TD_TEMPLATE % {
-    'result': esc('Matcher<%s>' % result_type),
+    'result': docs_result_type,
     'name': name,
     'args': esc(args),
     'comment': esc(strip_doxygen(comment)),
@@ -135,7 +142,7 @@
   # exclude known narrowing matchers that also take other matchers as
   # arguments.
   elif ('Matcher<' not in args or
-        name in ['allOf', 'anyOf', 'anything', 'unless']):
+        name in ['allOf', 'anyOf', 'anything', 'unless', 'mapAnyOf']):
     dict = narrowing_matchers
     lookup = result_type + name + esc(args)
   else:
diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h
--- a/clang/include/clang/ASTMatchers/ASTMatchers.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchers.h
@@ -847,6 +847,12 @@
       TK, InnerMatcher);
 }
 
+template 
+internal::Matcher::Type>
+traverse(TraversalKind TK, const internal::MapAnyOfHelper &InnerMatcher) {
+  return traverse(TK, InnerMatcher);
+}
+
 /// Matches expressions that match InnerMatcher after any implicit AST
 /// nodes are stripped off.
 ///
@@ -2693,6 +2699,36 @@
                                                    UnaryExprOrTypeTraitExpr>
     unaryExprOrTypeTraitExpr;
 
+/// Matches any of the \p NodeMatchers with InnerMatchers nested within
+///
+/// Given
+/// \code
+///   if (true);
+///   for (; true; );
+/// \endcode
+/// with the matcher
+/// \code
+///   mapAnyOf(ifStmt, forStmt).with(
+///     hasCondition(cxxBoolLiteralExpr(equals(true)))
+///     ).bind("trueCond")
+/// \endcode
+/// matches the \c if and the \c for. It is equivalent to:
+/// \code
+///   auto trueCond = hasCondition(cxxBoolLiteralExpr(equals(true)));
+///   anyOf(
+///     ifStmt(trueCond).bind("trueCond"),
+///     forStmt(trueCond).bind("trueCond")
+///     );
+/// \endcode
+///
+/// The with() chain-call accepts zero or more matchers which are combined
+/// as-if with allOf() in each of the node matchers.
+/// Usable as: Any Matcher
+template 
+auto mapAnyOf(internal::VariadicDynCastAllOfMatcher const &...) {
+  return internal::MapAnyOfHelper();
+}
+
 /// Matches unary expressions that have a specific type of argument.
 ///
 /// Given
diff --git a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
--- a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
@@ -1987,6 +1987,66 @@
                                                   llvm::Regex::RegexFlags Flags,
                                                   StringRef MatcherID);
 
+template 
+constexpr auto applyMatcherImpl(F &&f, Tuple &&args,
+                                std::index_sequence) {
+  return std::forward(f)(std::get(std::forward(args))...);
+}
+
+template 
+constexpr auto applyMatcher(F &&f, Tuple &&args) {
+  return applyMatcherImpl(
+      std::forward(f), std::forward(args),
+      std::make_index_sequence<
+          std::tuple_size::type>::value>());
+}
+
+template 
+struct GetCladeImpl {
+  using Type = Head;
+};
+template 
+struct GetCladeImpl
+    : GetCladeImpl::value,
+                   typename Tail::head, typename Tail::tail> {};
+
+template 
+struct GetClade : GetCladeImpl {};
+
+template 
+struct MapAnyOfMatcherImpl {
+
+  template 
+  BindableMatcher
+  operator()(InnerMatchers &&... InnerMatcher) const {
+    // TODO: Use std::apply from c++17
+    return VariadicAllOfMatcher()(applyMatcher(
+        internal::VariadicOperatorMatcherFunc<
+            0, std::numeric_limits::max()>{
+            internal::DynTypedMatcher::VO_AnyOf},
+        applyMatcher(
+            [&](auto... Matcher) {
+              return std::make_tuple(Matcher(
+                  std::forward(InnerMatcher)...)...);
+            },
+            std::tuple<
+                VariadicDynCastAllOfMatcher...>())));
+  }
+};
+
+template 
+using MapAnyOfMatcher =
+    MapAnyOfMatcherImpl::Type,
+                        MatcherTypes...>;
+
+template  struct MapAnyOfHelper {
+  using CladeType = typename GetClade::Type;
+
+  MapAnyOfMatcher with;
+
+  operator BindableMatcher() const { return with(); }
+};
+
 } // namespace internal
 
 } // namespace ast_matchers
diff --git a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
--- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
@@ -465,6 +465,59 @@
               cxxCatchStmt(anyOf(hasDescendant(varDecl()), isCatchAll()))));
 }
 
+TEST_P(ASTMatchersTest, MapAnyOf) {
+  if (!GetParam().isCXX()) {
+    return;
+  }
+
+  StringRef Code = R"cpp(
+void F() {
+  if (true) {}
+  for ( ; false; ) {}
+}
+)cpp";
+
+  auto trueExpr = cxxBoolLiteral(equals(true));
+  auto falseExpr = cxxBoolLiteral(equals(false));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(ifStmt, forStmt).with(hasCondition(trueExpr)))));
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(ifStmt, forStmt).with(hasCondition(falseExpr)))));
+
+  Code = R"cpp(
+void func(bool b) {}
+struct S {
+  S(bool b) {}
+};
+void F() {
+  func(false);
+  S s(true);
+}
+)cpp";
+  EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                                     mapAnyOf(callExpr, cxxConstructExpr)
+                                         .with(hasArgument(0, trueExpr)))));
+  EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                                     mapAnyOf(callExpr, cxxConstructExpr)
+                                         .with(hasArgument(0, falseExpr)))));
+
+  EXPECT_TRUE(
+      matches(Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                             mapAnyOf(callExpr, cxxConstructExpr)
+                                 .with(hasArgument(0, expr()),
+                                       hasDeclaration(functionDecl())))));
+
+  EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                                     mapAnyOf(callExpr, cxxConstructExpr))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(callExpr, cxxConstructExpr).bind("call"))));
+}
+
 TEST_P(ASTMatchersTest, IsDerivedFrom) {
   if (!GetParam().isCXX()) {
     return;