From dbe056c2e37f00b9f33ab63bba73dbb004e13562 Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Sat, 2 Jan 2021 00:01:03 +0000 Subject: [PATCH] [ASTMatchers] Make cxxOperatorCallExpr matchers API-compatible with n-ary operators This makes them composable with mapAnyOf(). Differential Revision: https://reviews.llvm.org/D94128 --- clang/docs/LibASTMatchersReference.html | 82 +++++++- clang/include/clang/ASTMatchers/ASTMatchers.h | 46 ++-- .../clang/ASTMatchers/ASTMatchersInternal.h | 198 ++++++++++++++++-- .../ASTMatchers/ASTMatchersNarrowingTest.cpp | 232 +++++++++++++++++++++ .../ASTMatchers/ASTMatchersTraversalTest.cpp | 133 ++++++++++++ 5 files changed, 651 insertions(+), 40 deletions(-) diff --git a/clang/docs/LibASTMatchersReference.html b/clang/docs/LibASTMatchersReference.html index e5de645266b..497e2d4584a 100644 --- a/clang/docs/LibASTMatchersReference.html +++ b/clang/docs/LibASTMatchersReference.html @@ -3232,6 +3232,16 @@ cxxNewExpr(isArray()) +Matcher<CXXOperatorCallExpr>hasAnyOperatorNameStringRef, ..., StringRef +
Matches operator expressions (binary or unary) that have any of the
+specified names.
+
+   hasAnyOperatorName("+", "-")
+ Is equivalent to
+   anyOf(hasOperatorName("+"), hasOperatorName("-"))
+
+ + Matcher<CXXOperatorCallExpr>hasAnyOverloadedOperatorNameStringRef, ..., StringRef
Matches overloaded operator names.
 
@@ -3244,6 +3254,15 @@ Is equivalent to
 
+Matcher<CXXOperatorCallExpr>hasOperatorNamestd::string Name +
Matches the operator Name of operator expressions (binary or
+unary).
+
+Example matches a || b (matcher = binaryOperator(hasOperatorName("||")))
+  !(a || b)
+
+ + Matcher<CXXOperatorCallExpr>hasOverloadedOperatorNameStringRef Name
Matches overloaded operator names.
 
@@ -5152,8 +5171,8 @@ should be passed as a quoted string. e.g., ofKind("UETT_SizeOf").
 
-Matcher<UnaryOperator>hasAnyOperatorNameStringRef, ..., StringRef -
Matches operator expressions (binary or unary) that have any of the
+Matcher<UnaryOperator>hasAnyOperatorNameStringRef, ..., StringRef
+
Matches operator expressions (binary or unary) that have any of the
 specified names.
 
    hasAnyOperatorName("+", "-")
@@ -5162,8 +5181,8 @@ specified names.
 
-Matcher<UnaryOperator>hasOperatorNamestd::string Name -
Matches the operator Name of operator expressions (binary or
+Matcher<UnaryOperator>hasOperatorNamestd::string Name
+
Matches the operator Name of operator expressions (binary or
 unary).
 
 Example matches a || b (matcher = binaryOperator(hasOperatorName("||")))
@@ -5689,16 +5708,16 @@ arraySubscriptExpression(hasIndex(integerLiteral()))
 
-Matcher<ArraySubscriptExpr>hasLHSMatcher<Expr> InnerMatcher -
Matches the left hand side of binary operator expressions.
+Matcher<ArraySubscriptExpr>hasLHSMatcher<Expr> InnerMatcher
+
Matches the left hand side of binary operator expressions.
 
 Example matches a (matcher = binaryOperator(hasLHS()))
   a || b
 
-Matcher<ArraySubscriptExpr>hasRHSMatcher<Expr> InnerMatcher -
Matches the right hand side of binary operator expressions.
+Matcher<ArraySubscriptExpr>hasRHSMatcher<Expr> InnerMatcher
+
Matches the right hand side of binary operator expressions.
 
 Example matches b (matcher = binaryOperator(hasRHS()))
   a || b
@@ -5749,7 +5768,7 @@ Usable as: Matcher<BinaryOperator>hasEitherOperandMatcher<Expr>  InnerMatcher
+Matcher<BinaryOperator>hasEitherOperandMatcher<Expr> InnerMatcher
 
Matches if either the left hand side or the right hand side of a
 binary operator matches.
 
@@ -5763,7 +5782,7 @@ Example matches a (matcher = binaryOperator(hasLHS()))
-Matcher<BinaryOperator>hasOperandsMatcher<Expr> Matcher1, Matcher<Expr> Matcher2 +Matcher<BinaryOperator>hasOperandsMatcher<Expr> Matcher1, Matcher<Expr> Matcher2
Matches if both matchers match with opposite sides of the binary operator.
 
 Example matcher = binaryOperator(hasOperands(integerLiteral(equals(1),
@@ -6276,6 +6295,49 @@ cxxNewExpr(hasPlacementArg(1, integerLiteral(equals(16))))
 
+Matcher<CXXOperatorCallExpr>hasEitherOperandMatcher<Expr> InnerMatcher +
Matches if either the left hand side or the right hand side of a
+binary operator matches.
+
+ + +Matcher<CXXOperatorCallExpr>hasLHSMatcher<Expr> InnerMatcher +
Matches the left hand side of binary operator expressions.
+
+Example matches a (matcher = binaryOperator(hasLHS()))
+  a || b
+
+ + +Matcher<CXXOperatorCallExpr>hasOperandsMatcher<Expr> Matcher1, Matcher<Expr> Matcher2 +
Matches if both matchers match with opposite sides of the binary operator.
+
+Example matcher = binaryOperator(hasOperands(integerLiteral(equals(1),
+                                             integerLiteral(equals(2)))
+  1 + 2 // Match
+  2 + 1 // Match
+  1 + 1 // No match
+  2 + 2 // No match
+
+ + +Matcher<CXXOperatorCallExpr>hasRHSMatcher<Expr> InnerMatcher +
Matches the right hand side of binary operator expressions.
+
+Example matches b (matcher = binaryOperator(hasRHS()))
+  a || b
+
+ + +Matcher<CXXOperatorCallExpr>hasUnaryOperandMatcher<Expr> InnerMatcher +
Matches if the operand of a unary operator matches.
+
+Example matches true (matcher = hasUnaryOperand(
+                                  cxxBoolLiteral(equals(true))))
+  !true
+
+ + Matcher<CXXRecordDecl>hasAnyBaseMatcher<CXXBaseSpecifier> BaseSpecMatcher
Matches C++ classes that have a direct or indirect base matching BaseSpecMatcher.
 
diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h
index dd99e6a420a..e81ac4cb7bf 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchers.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchers.h
@@ -5197,9 +5197,12 @@ AST_POLYMORPHIC_MATCHER_P_OVERLOAD(equals,
 /// \endcode
 AST_POLYMORPHIC_MATCHER_P(hasOperatorName,
                           AST_POLYMORPHIC_SUPPORTED_TYPES(BinaryOperator,
+                                                          CXXOperatorCallExpr,
                                                           UnaryOperator),
                           std::string, Name) {
-  return Name == Node.getOpcodeStr(Node.getOpcode());
+  if (Optional OpName = internal::getOpName(Node))
+    return *OpName == Name;
+  return false;
 }
 
 /// Matches operator expressions (binary or unary) that have any of the
@@ -5211,7 +5214,8 @@ AST_POLYMORPHIC_MATCHER_P(hasOperatorName,
 extern const internal::VariadicFunction<
     internal::PolymorphicMatcherWithParam1<
         internal::HasAnyOperatorNameMatcher, std::vector,
-        AST_POLYMORPHIC_SUPPORTED_TYPES(BinaryOperator, UnaryOperator)>,
+        AST_POLYMORPHIC_SUPPORTED_TYPES(BinaryOperator, CXXOperatorCallExpr,
+                                        UnaryOperator)>,
     StringRef, internal::hasAnyOperatorNameFunc>
     hasAnyOperatorName;
 
@@ -5263,9 +5267,10 @@ AST_POLYMORPHIC_MATCHER(isComparisonOperator,
 /// \endcode
 AST_POLYMORPHIC_MATCHER_P(hasLHS,
                           AST_POLYMORPHIC_SUPPORTED_TYPES(BinaryOperator,
+                                                          CXXOperatorCallExpr,
                                                           ArraySubscriptExpr),
                           internal::Matcher, InnerMatcher) {
-  const Expr *LeftHandSide = Node.getLHS();
+  const Expr *LeftHandSide = internal::getLHS(Node);
   return (LeftHandSide != nullptr &&
           InnerMatcher.matches(*LeftHandSide, Finder, Builder));
 }
@@ -5278,18 +5283,23 @@ AST_POLYMORPHIC_MATCHER_P(hasLHS,
 /// \endcode
 AST_POLYMORPHIC_MATCHER_P(hasRHS,
                           AST_POLYMORPHIC_SUPPORTED_TYPES(BinaryOperator,
+                                                          CXXOperatorCallExpr,
                                                           ArraySubscriptExpr),
                           internal::Matcher, InnerMatcher) {
-  const Expr *RightHandSide = Node.getRHS();
+  const Expr *RightHandSide = internal::getRHS(Node);
   return (RightHandSide != nullptr &&
           InnerMatcher.matches(*RightHandSide, Finder, Builder));
 }
 
 /// Matches if either the left hand side or the right hand side of a
 /// binary operator matches.
-inline internal::Matcher hasEitherOperand(
-    const internal::Matcher &InnerMatcher) {
-  return anyOf(hasLHS(InnerMatcher), hasRHS(InnerMatcher));
+AST_POLYMORPHIC_MATCHER_P(hasEitherOperand,
+                          AST_POLYMORPHIC_SUPPORTED_TYPES(BinaryOperator,
+                                                          CXXOperatorCallExpr),
+                          internal::Matcher, InnerMatcher) {
+  return internal::VariadicDynCastAllOfMatcher()(
+             anyOf(hasLHS(InnerMatcher), hasRHS(InnerMatcher)))
+      .matches(Node, Finder, Builder);
 }
 
 /// Matches if both matchers match with opposite sides of the binary operator.
@@ -5302,11 +5312,15 @@ inline internal::Matcher hasEitherOperand(
 ///   1 + 1 // No match
 ///   2 + 2 // No match
 /// \endcode
-inline internal::Matcher
-hasOperands(const internal::Matcher &Matcher1,
-            const internal::Matcher &Matcher2) {
-  return anyOf(allOf(hasLHS(Matcher1), hasRHS(Matcher2)),
-               allOf(hasLHS(Matcher2), hasRHS(Matcher1)));
+AST_POLYMORPHIC_MATCHER_P2(hasOperands,
+                           AST_POLYMORPHIC_SUPPORTED_TYPES(BinaryOperator,
+                                                           CXXOperatorCallExpr),
+                           internal::Matcher, Matcher1,
+                           internal::Matcher, Matcher2) {
+  return internal::VariadicDynCastAllOfMatcher()(
+             anyOf(allOf(hasLHS(Matcher1), hasRHS(Matcher2)),
+                   allOf(hasLHS(Matcher2), hasRHS(Matcher1))))
+      .matches(Node, Finder, Builder);
 }
 
 /// Matches if the operand of a unary operator matches.
@@ -5316,9 +5330,11 @@ hasOperands(const internal::Matcher &Matcher1,
 /// \code
 ///   !true
 /// \endcode
-AST_MATCHER_P(UnaryOperator, hasUnaryOperand,
-              internal::Matcher, InnerMatcher) {
-  const Expr * const Operand = Node.getSubExpr();
+AST_POLYMORPHIC_MATCHER_P(hasUnaryOperand,
+                          AST_POLYMORPHIC_SUPPORTED_TYPES(UnaryOperator,
+                                                          CXXOperatorCallExpr),
+                          internal::Matcher, InnerMatcher) {
+  const Expr *const Operand = internal::getSubExpr(Node);
   return (Operand != nullptr &&
           InnerMatcher.matches(*Operand, Finder, Builder));
 }
diff --git a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
index 2c2b99ba918..7f97eb3cd43 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h
@@ -1882,6 +1882,160 @@ struct GetBodyMatcher
+inline Optional
+equivalentBinaryOperator(const NodeType &Node) {
+  return Node.getOpcode();
+}
+
+template <>
+inline Optional
+equivalentBinaryOperator(const CXXOperatorCallExpr &Node) {
+  if (Node.getNumArgs() != 2)
+    return None;
+  switch (Node.getOperator()) {
+  default:
+    return None;
+  case OO_ArrowStar:
+    return BO_PtrMemI;
+  case OO_Star:
+    return BO_Mul;
+  case OO_Slash:
+    return BO_Div;
+  case OO_Percent:
+    return BO_Rem;
+  case OO_Plus:
+    return BO_Add;
+  case OO_Minus:
+    return BO_Sub;
+  case OO_LessLess:
+    return BO_Shl;
+  case OO_GreaterGreater:
+    return BO_Shr;
+  case OO_Spaceship:
+    return BO_Cmp;
+  case OO_Less:
+    return BO_LT;
+  case OO_Greater:
+    return BO_GT;
+  case OO_LessEqual:
+    return BO_LE;
+  case OO_GreaterEqual:
+    return BO_GE;
+  case OO_EqualEqual:
+    return BO_EQ;
+  case OO_ExclaimEqual:
+    return BO_NE;
+  case OO_Amp:
+    return BO_And;
+  case OO_Caret:
+    return BO_Xor;
+  case OO_Pipe:
+    return BO_Or;
+  case OO_AmpAmp:
+    return BO_LAnd;
+  case OO_PipePipe:
+    return BO_LOr;
+  case OO_Equal:
+    return BO_Assign;
+  case OO_StarEqual:
+    return BO_MulAssign;
+  case OO_SlashEqual:
+    return BO_DivAssign;
+  case OO_PercentEqual:
+    return BO_RemAssign;
+  case OO_PlusEqual:
+    return BO_AddAssign;
+  case OO_MinusEqual:
+    return BO_SubAssign;
+  case OO_LessLessEqual:
+    return BO_ShlAssign;
+  case OO_GreaterGreaterEqual:
+    return BO_ShrAssign;
+  case OO_AmpEqual:
+    return BO_AndAssign;
+  case OO_CaretEqual:
+    return BO_XorAssign;
+  case OO_PipeEqual:
+    return BO_OrAssign;
+  case OO_Comma:
+    return BO_Comma;
+  }
+}
+
+template 
+inline Optional
+equivalentUnaryOperator(const NodeType &Node) {
+  return Node.getOpcode();
+}
+
+template <>
+inline Optional
+equivalentUnaryOperator(const CXXOperatorCallExpr &Node) {
+  if (Node.getNumArgs() != 1)
+    return None;
+  switch (Node.getOperator()) {
+  default:
+    return None;
+  case OO_Plus:
+    return UO_Plus;
+  case OO_Minus:
+    return UO_Minus;
+  case OO_Amp:
+    return UO_AddrOf;
+  case OO_Tilde:
+    return UO_Not;
+  case OO_Exclaim:
+    return UO_LNot;
+  case OO_PlusPlus: {
+    const auto *FD = Node.getDirectCallee();
+    if (!FD)
+      return None;
+    return FD->getNumParams() > 0 ? UO_PostInc : UO_PreInc;
+  }
+  case OO_MinusMinus: {
+    const auto *FD = Node.getDirectCallee();
+    if (!FD)
+      return None;
+    return FD->getNumParams() > 0 ? UO_PostDec : UO_PreDec;
+  }
+  case OO_Coawait:
+    return UO_Coawait;
+  }
+}
+
+template  inline const Expr *getLHS(const NodeType &Node) {
+  return Node.getLHS();
+}
+template <>
+inline const Expr *
+getLHS(const CXXOperatorCallExpr &Node) {
+  if (!internal::equivalentBinaryOperator(Node))
+    return nullptr;
+  return Node.getArg(0);
+}
+template  inline const Expr *getRHS(const NodeType &Node) {
+  return Node.getRHS();
+}
+template <>
+inline const Expr *
+getRHS(const CXXOperatorCallExpr &Node) {
+  if (!internal::equivalentBinaryOperator(Node))
+    return nullptr;
+  return Node.getArg(1);
+}
+template 
+inline const Expr *getSubExpr(const NodeType &Node) {
+  return Node.getSubExpr();
+}
+template <>
+inline const Expr *
+getSubExpr(const CXXOperatorCallExpr &Node) {
+  if (!internal::equivalentUnaryOperator(Node))
+    return nullptr;
+  return Node.getArg(0);
+}
+
 template 
 struct HasSizeMatcher {
   static bool hasSize(const Ty &Node, unsigned int N) {
@@ -1929,6 +2083,23 @@ llvm::Optional
 getExpansionLocOfMacro(StringRef MacroName, SourceLocation Loc,
                        const ASTContext &Context);
 
+inline Optional getOpName(const UnaryOperator &Node) {
+  return Node.getOpcodeStr(Node.getOpcode());
+}
+inline Optional getOpName(const BinaryOperator &Node) {
+  return Node.getOpcodeStr();
+}
+inline Optional getOpName(const CXXOperatorCallExpr &Node) {
+  auto optBinaryOpcode = equivalentBinaryOperator(Node);
+  if (!optBinaryOpcode) {
+    auto optUnaryOpcode = equivalentUnaryOperator(Node);
+    if (!optUnaryOpcode)
+      return None;
+    return UnaryOperator::getOpcodeStr(*optUnaryOpcode);
+  }
+  return BinaryOperator::getOpcodeStr(*optBinaryOpcode);
+}
+
 /// Matches overloaded operators with a specific name.
 ///
 /// The type argument ArgT is not used by this matcher but is used by
@@ -1936,8 +2107,10 @@ getExpansionLocOfMacro(StringRef MacroName, SourceLocation Loc,
 template >
 class HasAnyOperatorNameMatcher : public SingleNodeMatcherInterface {
   static_assert(std::is_same::value ||
+                    std::is_same::value ||
                     std::is_same::value,
-                "Matcher only supports `BinaryOperator` and `UnaryOperator`");
+                "Matcher only supports `BinaryOperator`, `UnaryOperator` and "
+                "`CXXOperatorCallExpr`");
   static_assert(std::is_same>::value,
                 "Matcher ArgT must be std::vector");
 
@@ -1946,26 +2119,21 @@ public:
       : SingleNodeMatcherInterface(), Names(std::move(Names)) {}
 
   bool matchesNode(const T &Node) const override {
-    StringRef OpName = getOpName(Node);
-    return llvm::any_of(
-        Names, [&](const std::string &Name) { return Name == OpName; });
+    Optional OptOpName = getOpName(Node);
+    if (!OptOpName)
+      return false;
+    return llvm::any_of(Names, [OpName = *OptOpName](const std::string &Name) {
+      return Name == OpName;
+    });
   }
 
 private:
-  static StringRef getOpName(const UnaryOperator &Node) {
-    return Node.getOpcodeStr(Node.getOpcode());
-  }
-  static StringRef getOpName(const BinaryOperator &Node) {
-    return Node.getOpcodeStr();
-  }
-
   const std::vector Names;
 };
 
-using HasOpNameMatcher =
-    PolymorphicMatcherWithParam1,
-                                 void(TypeList)>;
+using HasOpNameMatcher = PolymorphicMatcherWithParam1<
+    HasAnyOperatorNameMatcher, std::vector,
+    void(TypeList)>;
 
 HasOpNameMatcher hasAnyOperatorNameFunc(ArrayRef NameRefs);
 
diff --git a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
index 49b010feeb7..9e011892f6e 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
@@ -516,6 +516,238 @@ void F() {
   EXPECT_TRUE(matches(
       Code, traverse(TK_IgnoreUnlessSpelledInSource,
                      mapAnyOf(callExpr, cxxConstructExpr).bind("call"))));
+
+  Code = R"cpp(
+struct HasOpNeqMem
+{
+    bool operator!=(const HasOpNeqMem& other) const
+    {
+        return true;
+    }
+};
+struct HasOpFree
+{
+};
+bool operator!=(const HasOpFree& lhs, const HasOpFree& rhs)
+{
+    return true;
+}
+
+void binop()
+{
+    int s1;
+    int s2;
+    if (s1 != s2)
+        return;
+}
+
+void opMem()
+{
+    HasOpNeqMem s1;
+    HasOpNeqMem s2;
+    if (s1 != s2)
+        return;
+}
+
+void opFree()
+{
+    HasOpFree s1;
+    HasOpFree s2;
+    if (s1 != s2)
+        return;
+}
+)cpp";
+
+  EXPECT_TRUE(matches(
+      Code,
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+                   .with(hasOperatorName("!="),
+                         forFunction(functionDecl(hasName("binop"))),
+                         hasLHS(declRefExpr(to(varDecl(hasName("s1"))))),
+                         hasRHS(declRefExpr(to(varDecl(hasName("s2")))))))));
+
+  EXPECT_TRUE(matches(
+      Code,
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+                   .with(hasOperatorName("!="),
+                         forFunction(functionDecl(hasName("opMem"))),
+                         hasLHS(declRefExpr(to(varDecl(hasName("s1"))))),
+                         hasRHS(declRefExpr(to(varDecl(hasName("s2")))))))));
+
+  EXPECT_TRUE(matches(
+      Code,
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+                   .with(hasOperatorName("!="),
+                         forFunction(functionDecl(hasName("opFree"))),
+                         hasLHS(declRefExpr(to(varDecl(hasName("s1"))))),
+                         hasRHS(declRefExpr(to(varDecl(hasName("s2")))))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+                         .with(hasOperatorName("!="),
+                               forFunction(functionDecl(hasName("binop"))),
+                               hasEitherOperand(
+                                   declRefExpr(to(varDecl(hasName("s1"))))),
+                               hasEitherOperand(
+                                   declRefExpr(to(varDecl(hasName("s2")))))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+                         .with(hasOperatorName("!="),
+                               forFunction(functionDecl(hasName("opMem"))),
+                               hasEitherOperand(
+                                   declRefExpr(to(varDecl(hasName("s1"))))),
+                               hasEitherOperand(
+                                   declRefExpr(to(varDecl(hasName("s2")))))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+                         .with(hasOperatorName("!="),
+                               forFunction(functionDecl(hasName("opFree"))),
+                               hasEitherOperand(
+                                   declRefExpr(to(varDecl(hasName("s1"))))),
+                               hasEitherOperand(
+                                   declRefExpr(to(varDecl(hasName("s2")))))))));
+
+  EXPECT_TRUE(matches(
+      Code,
+      traverse(
+          TK_IgnoreUnlessSpelledInSource,
+          mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+              .with(hasOperatorName("!="),
+                    forFunction(functionDecl(hasName("binop"))),
+                    hasOperands(declRefExpr(to(varDecl(hasName("s1")))),
+                                declRefExpr(to(varDecl(hasName("s2"))))),
+                    hasOperands(declRefExpr(to(varDecl(hasName("s2")))),
+                                declRefExpr(to(varDecl(hasName("s1")))))))));
+
+  EXPECT_TRUE(matches(
+      Code,
+      traverse(
+          TK_IgnoreUnlessSpelledInSource,
+          mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+              .with(hasOperatorName("!="),
+                    forFunction(functionDecl(hasName("opMem"))),
+                    hasOperands(declRefExpr(to(varDecl(hasName("s1")))),
+                                declRefExpr(to(varDecl(hasName("s2"))))),
+                    hasOperands(declRefExpr(to(varDecl(hasName("s2")))),
+                                declRefExpr(to(varDecl(hasName("s1")))))))));
+
+  EXPECT_TRUE(matches(
+      Code,
+      traverse(
+          TK_IgnoreUnlessSpelledInSource,
+          mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+              .with(hasOperatorName("!="),
+                    forFunction(functionDecl(hasName("opFree"))),
+                    hasOperands(declRefExpr(to(varDecl(hasName("s1")))),
+                                declRefExpr(to(varDecl(hasName("s2"))))),
+                    hasOperands(declRefExpr(to(varDecl(hasName("s2")))),
+                                declRefExpr(to(varDecl(hasName("s1")))))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+                         .with(hasAnyOperatorName("==", "!="),
+                               forFunction(functionDecl(hasName("binop")))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+                         .with(hasAnyOperatorName("==", "!="),
+                               forFunction(functionDecl(hasName("opMem")))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(binaryOperator, cxxOperatorCallExpr)
+                         .with(hasAnyOperatorName("==", "!="),
+                               forFunction(functionDecl(hasName("opFree")))))));
+
+  Code = R"cpp(
+struct HasOpBangMem
+{
+    bool operator!() const
+    {
+        return false;
+    }
+};
+struct HasOpBangFree
+{
+};
+bool operator!(HasOpBangFree const&)
+{
+    return false;
+}
+
+void unop()
+{
+    int s1;
+    if (!s1)
+        return;
+}
+
+void opMem()
+{
+    HasOpBangMem s1;
+    if (!s1)
+        return;
+}
+
+void opFree()
+{
+    HasOpBangFree s1;
+    if (!s1)
+        return;
+}
+)cpp";
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(unaryOperator, cxxOperatorCallExpr)
+                         .with(hasOperatorName("!"),
+                               forFunction(functionDecl(hasName("unop"))),
+                               hasUnaryOperand(
+                                   declRefExpr(to(varDecl(hasName("s1")))))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(unaryOperator, cxxOperatorCallExpr)
+                         .with(hasOperatorName("!"),
+                               forFunction(functionDecl(hasName("opMem"))),
+                               hasUnaryOperand(
+                                   declRefExpr(to(varDecl(hasName("s1")))))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(unaryOperator, cxxOperatorCallExpr)
+                         .with(hasOperatorName("!"),
+                               forFunction(functionDecl(hasName("opFree"))),
+                               hasUnaryOperand(
+                                   declRefExpr(to(varDecl(hasName("s1")))))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(unaryOperator, cxxOperatorCallExpr)
+                         .with(hasAnyOperatorName("+", "!"),
+                               forFunction(functionDecl(hasName("unop")))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(unaryOperator, cxxOperatorCallExpr)
+                         .with(hasAnyOperatorName("+", "!"),
+                               forFunction(functionDecl(hasName("opMem")))))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     mapAnyOf(unaryOperator, cxxOperatorCallExpr)
+                         .with(hasAnyOperatorName("+", "!"),
+                               forFunction(functionDecl(hasName("opFree")))))));
 }
 
 TEST_P(ASTMatchersTest, IsDerivedFrom) {
diff --git a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
index 19ab6187d96..06bcd65d891 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
@@ -1313,6 +1313,85 @@ TEST(MatchBinaryOperator, HasLHSAndHasRHS) {
       traverse(TK_AsIs, hasRHS(hasType(pointsTo(qualType())))));
   EXPECT_TRUE(matches("void x() { 1[\"abc\"]; }", OperatorIntPointer));
   EXPECT_TRUE(notMatches("void x() { \"abc\"[1]; }", OperatorIntPointer));
+
+  StringRef Code = R"cpp(
+struct HasOpEqMem
+{
+    bool operator==(const HasOpEqMem& other) const
+    {
+        return true;
+    }
+};
+
+struct HasOpFree
+{
+};
+bool operator==(const HasOpFree& lhs, const HasOpFree& rhs)
+{
+    return true;
+}
+
+void opMem()
+{
+    HasOpEqMem s1;
+    HasOpEqMem s2;
+    if (s1 == s2)
+        return;
+}
+
+void opFree()
+{
+    HasOpFree s1;
+    HasOpFree s2;
+    if (s1 == s2)
+        return;
+}
+)cpp";
+  auto s1Expr = declRefExpr(to(varDecl(hasName("s1"))));
+  auto s2Expr = declRefExpr(to(varDecl(hasName("s2"))));
+  EXPECT_TRUE(matches(
+      Code,
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               cxxOperatorCallExpr(forFunction(functionDecl(hasName("opMem"))),
+                                   hasOperatorName("=="), hasLHS(s1Expr),
+                                   hasRHS(s2Expr)))));
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     cxxOperatorCallExpr(
+                         forFunction(functionDecl(hasName("opMem"))),
+                         hasAnyOperatorName("!=", "=="), hasLHS(s1Expr)))));
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     cxxOperatorCallExpr(
+                         forFunction(functionDecl(hasName("opMem"))),
+                         hasOperatorName("=="), hasOperands(s1Expr, s2Expr)))));
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     cxxOperatorCallExpr(
+                         forFunction(functionDecl(hasName("opMem"))),
+                         hasOperatorName("=="), hasEitherOperand(s2Expr)))));
+
+  EXPECT_TRUE(matches(
+      Code,
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               cxxOperatorCallExpr(forFunction(functionDecl(hasName("opFree"))),
+                                   hasOperatorName("=="), hasLHS(s1Expr),
+                                   hasRHS(s2Expr)))));
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     cxxOperatorCallExpr(
+                         forFunction(functionDecl(hasName("opFree"))),
+                         hasAnyOperatorName("!=", "=="), hasLHS(s1Expr)))));
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     cxxOperatorCallExpr(
+                         forFunction(functionDecl(hasName("opFree"))),
+                         hasOperatorName("=="), hasOperands(s1Expr, s2Expr)))));
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     cxxOperatorCallExpr(
+                         forFunction(functionDecl(hasName("opFree"))),
+                         hasOperatorName("=="), hasEitherOperand(s2Expr)))));
 }
 
 TEST(MatchBinaryOperator, HasEitherOperand) {
@@ -1461,6 +1540,60 @@ TEST(MatchUnaryOperator, HasUnaryOperand) {
 
   EXPECT_TRUE(matches("void x() { !false; }", OperatorOnFalse));
   EXPECT_TRUE(notMatches("void x() { !true; }", OperatorOnFalse));
+
+  StringRef Code = R"cpp(
+struct HasOpBangMem
+{
+    bool operator!() const
+    {
+        return false;
+    }
+};
+struct HasOpBangFree
+{
+};
+bool operator!(HasOpBangFree const&)
+{
+    return false;
+}
+
+void opMem()
+{
+    HasOpBangMem s1;
+    if (!s1)
+        return;
+}
+void opFree()
+{
+    HasOpBangFree s1;
+    if (!s1)
+        return;
+}
+)cpp";
+  auto s1Expr = declRefExpr(to(varDecl(hasName("s1"))));
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     cxxOperatorCallExpr(
+                         forFunction(functionDecl(hasName("opMem"))),
+                         hasOperatorName("!"), hasUnaryOperand(s1Expr)))));
+  EXPECT_TRUE(matches(
+      Code,
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               cxxOperatorCallExpr(forFunction(functionDecl(hasName("opMem"))),
+                                   hasAnyOperatorName("+", "!"),
+                                   hasUnaryOperand(s1Expr)))));
+
+  EXPECT_TRUE(matches(
+      Code, traverse(TK_IgnoreUnlessSpelledInSource,
+                     cxxOperatorCallExpr(
+                         forFunction(functionDecl(hasName("opFree"))),
+                         hasOperatorName("!"), hasUnaryOperand(s1Expr)))));
+  EXPECT_TRUE(matches(
+      Code,
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               cxxOperatorCallExpr(forFunction(functionDecl(hasName("opFree"))),
+                                   hasAnyOperatorName("+", "!"),
+                                   hasUnaryOperand(s1Expr)))));
 }
 
 TEST(Matcher, UnaryOperatorTypes) {
-- 
2.11.0