diff --git a/clang/docs/LibASTMatchersReference.html b/clang/docs/LibASTMatchersReference.html --- a/clang/docs/LibASTMatchersReference.html +++ b/clang/docs/LibASTMatchersReference.html @@ -1206,6 +1206,7 @@ Example matches a || b !(a || b) +See also the binaryOperation() matcher for more-general matching. @@ -1505,6 +1506,8 @@ ostream &operator<< (ostream &out, int i) { }; ostream &o; int b = 1, c = 1; o << b << c; +See also the binaryOperation() matcher for more-general matching of binary +uses of this AST node. @@ -5438,6 +5441,48 @@ Return typeNameParameters +Matcher<*>binaryOperationMatcher<*>...Matcher<*> +
Matches nodes which can be used with binary operators.
+
+The code
+  var1 != var2;
+might be represented in the clang AST as a binaryOperator or a
+cxxOperatorCallExpr, depending on
+
+* whether the types of var1 and var2 are fundamental (binaryOperator) or at
+  least one is a class type (cxxOperatorCallExpr)
+* whether the code appears in a template declaration, if at least one of the
+  vars is a dependent-type (binaryOperator)
+
+This matcher elides details in places where the matchers for the nodes are
+compatible.
+
+Given
+  binaryOperation(
+    hasOperatorName("!="),
+    hasLHS(expr().bind("lhs")),
+    hasRHS(expr().bind("rhs"))
+  )
+matches each use of "!=" in:
+  struct S{
+      bool operator!=(const S&) const;
+  };
+
+  void foo()
+  {
+     1 != 2;
+     S() != S();
+  }
+
+  template<typename T>
+  void templ()
+  {
+     1 != 2;
+     T() != S();
+  }
+
+ + Matcher<*>eachOfMatcher<*>, ..., Matcher<*>
Matches if any of the given matchers matches.
 
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
@@ -379,6 +379,14 @@
         add_matcher('*', name, 'Matcher<*>, ..., Matcher<*>', comment)
         return
 
+    m = re.match(
+        r"""^.*MapAnyOfMatcher<.*>\s*
+              ([a-zA-Z]*);$""",
+        declaration, flags=re.X)
+    if m:
+      name = m.groups()[0]
+      add_matcher('*', name, 'Matcher<*>...Matcher<*>', comment)
+      return
 
     # Parse free standing matcher functions, like:
     #   Matcher Name(Matcher InnerMatcher) {
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
@@ -1971,6 +1971,8 @@
 ///   ostream &o; int b = 1, c = 1;
 ///   o << b << c;
 /// \endcode
+/// See also the binaryOperation() matcher for more-general matching of binary
+/// uses of this AST node.
 extern const internal::VariadicDynCastAllOfMatcher
     cxxOperatorCallExpr;
 
@@ -2393,6 +2395,7 @@
 /// \code
 ///   !(a || b)
 /// \endcode
+/// See also the binaryOperation() matcher for more-general matching.
 extern const internal::VariadicDynCastAllOfMatcher
     binaryOperator;
 
@@ -2729,6 +2732,53 @@
   return internal::MapAnyOfHelper();
 }
 
+/// Matches nodes which can be used with binary operators.
+///
+/// The code
+/// \code
+///   var1 != var2;
+/// \endcode
+/// might be represented in the clang AST as a binaryOperator or a
+/// cxxOperatorCallExpr, depending on
+///
+/// * whether the types of var1 and var2 are fundamental (binaryOperator) or at
+///   least one is a class type (cxxOperatorCallExpr)
+/// * whether the code appears in a template declaration, if at least one of the
+///   vars is a dependent-type (binaryOperator)
+///
+/// This matcher elides details in places where the matchers for the nodes are
+/// compatible.
+///
+/// Given
+/// \code
+///   binaryOperation(
+///     hasOperatorName("!="),
+///     hasLHS(expr().bind("lhs")),
+///     hasRHS(expr().bind("rhs"))
+///   )
+/// \endcode
+/// matches each use of "!=" in:
+/// \code
+///   struct S{
+///       bool operator!=(const S&) const;
+///   };
+///
+///   void foo()
+///   {
+///      1 != 2;
+///      S() != S();
+///   }
+///
+///   template
+///   void templ()
+///   {
+///      1 != 2;
+///      T() != S();
+///   }
+/// \endcode
+extern const internal::MapAnyOfMatcher
+    binaryOperation;
+
 /// Matches unary expressions that have a specific type of argument.
 ///
 /// Given
diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
--- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
+++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
@@ -919,6 +919,8 @@
 const internal::VariadicDynCastAllOfMatcher stmtExpr;
 const internal::VariadicDynCastAllOfMatcher
     binaryOperator;
+const internal::MapAnyOfMatcher
+    binaryOperation;
 const internal::VariadicDynCastAllOfMatcher unaryOperator;
 const internal::VariadicDynCastAllOfMatcher
     conditionalOperator;
diff --git a/clang/lib/ASTMatchers/Dynamic/Marshallers.h b/clang/lib/ASTMatchers/Dynamic/Marshallers.h
--- a/clang/lib/ASTMatchers/Dynamic/Marshallers.h
+++ b/clang/lib/ASTMatchers/Dynamic/Marshallers.h
@@ -925,6 +925,50 @@
   const StringRef MatcherName;
 };
 
+template 
+class MapAnyOfMatcherDescriptor : public MatcherDescriptor {
+  std::vector Funcs;
+
+public:
+  MapAnyOfMatcherDescriptor(StringRef MatcherName)
+      : Funcs{DynCastAllOfMatcherDescriptor(
+            ast_matchers::internal::VariadicDynCastAllOfMatcher{},
+            MatcherName)...} {}
+
+  VariantMatcher create(SourceRange NameRange, ArrayRef Args,
+                        Diagnostics *Error) const override {
+    std::vector InnerArgs;
+
+    for (auto const &F : Funcs) {
+      InnerArgs.push_back(F.create(NameRange, Args, Error));
+      if (!Error->errors().empty())
+        return {};
+    }
+    return VariantMatcher::SingleMatcher(
+        ast_matchers::internal::BindableMatcher(
+            VariantMatcher::VariadicOperatorMatcher(
+                ast_matchers::internal::DynTypedMatcher::VO_AnyOf,
+                std::move(InnerArgs))
+                .getTypedMatcher()));
+  }
+
+  bool isVariadic() const override { return true; }
+  unsigned getNumArgs() const override { return 0; }
+
+  void getArgKinds(ASTNodeKind ThisKind, unsigned,
+                   std::vector &Kinds) const override {
+    Kinds.push_back(ThisKind);
+  }
+
+  bool isConvertibleTo(ASTNodeKind Kind, unsigned *Specificity,
+                       ASTNodeKind *LeastDerivedKind) const override {
+    return llvm::all_of(Funcs, [=](const auto &F) {
+      return F.isConvertibleTo(Kind, Specificity, LeastDerivedKind);
+    });
+  }
+};
+
 /// Helper functions to select the appropriate marshaller functions.
 /// They detect the number of arguments, arguments types and return type.
 
@@ -1029,6 +1073,14 @@
       MinCount, MaxCount, Func.Op, MatcherName);
 }
 
+template 
+std::unique_ptr makeMatcherAutoMarshall(
+    ast_matchers::internal::MapAnyOfMatcherImpl,
+    StringRef MatcherName) {
+  return std::make_unique>(
+      MatcherName);
+}
+
 } // namespace internal
 } // namespace dynamic
 } // namespace ast_matchers
diff --git a/clang/lib/ASTMatchers/Dynamic/Registry.cpp b/clang/lib/ASTMatchers/Dynamic/Registry.cpp
--- a/clang/lib/ASTMatchers/Dynamic/Registry.cpp
+++ b/clang/lib/ASTMatchers/Dynamic/Registry.cpp
@@ -143,6 +143,7 @@
   REGISTER_MATCHER(autoreleasePoolStmt)
   REGISTER_MATCHER(binaryConditionalOperator);
   REGISTER_MATCHER(binaryOperator);
+  REGISTER_MATCHER(binaryOperation);
   REGISTER_MATCHER(blockDecl);
   REGISTER_MATCHER(blockExpr);
   REGISTER_MATCHER(blockPointerType);
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
@@ -470,6 +470,10 @@
     return;
   }
 
+  if (GetParam().hasDelayedTemplateParsing()) {
+    return;
+  }
+
   StringRef Code = R"cpp(
 void F() {
   if (true) {}
@@ -556,6 +560,15 @@
     if (s1 != s2)
         return;
 }
+
+template
+void templ()
+{
+    T s1;
+    T s2;
+    if (s1 != s2)
+        return;
+}
 )cpp";
 
   EXPECT_TRUE(matches(
@@ -669,6 +682,38 @@
                          .with(hasAnyOperatorName("==", "!="),
                                forFunction(functionDecl(hasName("opFree")))))));
 
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     binaryOperation(
+                         hasOperatorName("!="),
+                         forFunction(functionDecl(hasName("binop"))),
+                         hasLHS(declRefExpr(to(varDecl(hasName("s1"))))),
+                         hasRHS(declRefExpr(to(varDecl(hasName("s2")))))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     binaryOperation(
+                         hasOperatorName("!="),
+                         forFunction(functionDecl(hasName("opMem"))),
+                         hasLHS(declRefExpr(to(varDecl(hasName("s1"))))),
+                         hasRHS(declRefExpr(to(varDecl(hasName("s2")))))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     binaryOperation(
+                         hasOperatorName("!="),
+                         forFunction(functionDecl(hasName("opFree"))),
+                         hasLHS(declRefExpr(to(varDecl(hasName("s1"))))),
+                         hasRHS(declRefExpr(to(varDecl(hasName("s2")))))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     binaryOperation(
+                         hasOperatorName("!="),
+                         forFunction(functionDecl(hasName("templ"))),
+                         hasLHS(declRefExpr(to(varDecl(hasName("s1"))))),
+                         hasRHS(declRefExpr(to(varDecl(hasName("s2")))))))));
+
   Code = R"cpp(
 struct HasOpBangMem
 {