https://github.com/irishrover updated 
https://github.com/llvm/llvm-project/pull/221432

>From 6309090c92d31b4b76aa0941163741abff494ec4 Mon Sep 17 00:00:00 2001
From: Zinovy Nis <[email protected]>
Date: Sat, 5 Sep 2026 13:59:09 +0300
Subject: [PATCH 1/2] [clang-tidy] Fix De Morgan for overloaded comparisons

Before my patch:

(https://source.chromium.org/chromium/chromium/src/+/main:base/trace_event/memory_allocator_dump.cc;l=174)

```diff
-  if (!(name == rhs.name && units == rhs.units &&
-        entry_type == rhs.entry_type)) {
+  if (!name == rhs.name || !units == rhs.units ||
+        entry_type != rhs.entry_type) {
```

With the patch:

```diff
-  if (!(name == rhs.name && units == rhs.units &&
-        entry_type == rhs.entry_type)) {
+  if (name != rhs.name || units != rhs.units ||
+        entry_type != rhs.entry_type) {
```
---
 .../readability/SimplifyBooleanExprCheck.cpp  | 12 ++++++
 clang-tools-extra/docs/ReleaseNotes.md        |  6 ++-
 .../simplify-boolean-expr-demorgan.cpp        | 41 +++++++++++++++++++
 3 files changed, 58 insertions(+), 1 deletion(-)

diff --git 
a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp 
b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
index 3a63b6f84eed3..50ec10ca0cc24 100644
--- a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
@@ -916,6 +916,18 @@ static bool flipDemorganSide(SmallVectorImpl<FixItHint> 
&Fixes,
   }
   if (const auto *BinOp = dyn_cast<BinaryOperator>(E))
     return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO);
+  // Overloaded comparisons are represented as CXXOperatorCallExpr rather than
+  // BinaryOperator, so negate them by replacing their operator location.
+  if (const auto *OpCall = dyn_cast<CXXOperatorCallExpr>(E)) {
+    const StringRef NegatedOperator = negatedOperator(OpCall);
+    if (!NegatedOperator.empty()) {
+      if (OpCall->getOperatorLoc().isMacroID())
+        return true;
+      Fixes.push_back(FixItHint::CreateReplacement(OpCall->getOperatorLoc(),
+                                                   NegatedOperator));
+      return false;
+    }
+  }
   if (const auto *Paren = dyn_cast<ParenExpr>(E)) {
     if (const auto *BinOp = dyn_cast<BinaryOperator>(Paren->getSubExpr()))
       return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO, Paren);
diff --git a/clang-tools-extra/docs/ReleaseNotes.md 
b/clang-tools-extra/docs/ReleaseNotes.md
index 3373acbc1b4a1..021f928ff718a 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -271,7 +271,7 @@ infrastructure are described first, followed by 
tool-specific sections.
     typedef or type alias that provides the only name of an otherwise unnamed
     tag, such as `typedef enum {} MyEnum;`, against the style configured for
     that tag kind instead of the typedef or type alias style.
-    
+
   - Added support for naming lambda init-captures (e.g. `[Captured = Var]`) via
     the new `LambdaCapture` options. Simple, non-init captures continue to 
follow
     the naming style of the variable they capture.
@@ -296,6 +296,10 @@ infrastructure are described first, followed by 
tool-specific sections.
   <clang-tidy/checks/readability/redundant-parentheses>` check by fixing a 
false
   positive on the required parentheses of `typeof` and `typeof_unqual` 
operands.
 
+- Fixed {doc}`readability-simplify-boolean-expr
+  <clang-tidy/checks/readability/simplify-boolean-expr>` producing invalid
+  fixes when applying De Morgan's theorem to overloaded comparison operators.
+
 - Improved {doc}`readability-trailing-comma
   <clang-tidy/checks/readability/trailing-comma>` check:
 
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/readability/simplify-boolean-expr-demorgan.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/readability/simplify-boolean-expr-demorgan.cpp
index bab9e17a7775b..4f3660b4a1c46 100644
--- 
a/clang-tools-extra/test/clang-tidy/checkers/readability/simplify-boolean-expr-demorgan.cpp
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/readability/simplify-boolean-expr-demorgan.cpp
@@ -106,3 +106,44 @@ void foo(bool A1, bool A2, bool A3, bool A4) {
   // CHECK-FIXES-NEXT: X = A1 || (A2 && A3);
   // CHECK-FIXES-NEXT: X = A1 && (A2 || A3);
 }
+
+// Equality on a user-defined type is an overloaded operator, so its negation
+// must replace `==` with `!=`, rather than insert `!` before the left operand.
+namespace overloaded_comparisons {
+struct Entry {
+  struct String {
+    bool operator==(const String &) const;
+    bool operator!=(const String &) const;
+    bool operator<(const String &) const;
+    bool operator>(const String &) const;
+    bool operator<=(const String &) const;
+    bool operator>=(const String &) const;
+  };
+
+  String name;
+  String units;
+  int entry_type;
+
+  bool matches(const Entry &rhs) const {
+    return !(name == rhs.name && units == rhs.units &&
+             entry_type == rhs.entry_type);
+  }
+  // CHECK-MESSAGES: :[[@LINE-3]]:12: warning: boolean expression can be 
simplified by DeMorgan's theorem [readability-simplify-boolean-expr]
+  // CHECK-FIXES: return name != rhs.name || units != rhs.units ||
+  // CHECK-FIXES-NEXT:             entry_type != rhs.entry_type;
+
+  bool differsFrom(const Entry &rhs) const {
+    return !(name != rhs.name || entry_type != rhs.entry_type);
+  }
+  // CHECK-MESSAGES: :[[@LINE-2]]:12: warning: boolean expression can be 
simplified by DeMorgan's theorem [readability-simplify-boolean-expr]
+  // CHECK-FIXES: return name == rhs.name && entry_type == rhs.entry_type;
+
+  bool comparesTo(const Entry &rhs) const {
+    return !(name < rhs.name && units > rhs.units && name <= rhs.name &&
+             units >= rhs.units && entry_type < rhs.entry_type);
+  }
+  // CHECK-MESSAGES: :[[@LINE-3]]:12: warning: boolean expression can be 
simplified by DeMorgan's theorem [readability-simplify-boolean-expr]
+  // CHECK-FIXES: return name >= rhs.name || units <= rhs.units || name > 
rhs.name ||
+  // CHECK-FIXES-NEXT:             units < rhs.units || entry_type >= 
rhs.entry_type;
+};
+} // namespace overloaded_comparisons

>From c6e32b118362038536c2784ca65e33a4f34f1fe4 Mon Sep 17 00:00:00 2001
From: Zinovy Nis <[email protected]>
Date: Sat, 19 Sep 2026 13:51:13 +0300
Subject: [PATCH 2/2] [clang-tidy] Apply the De Morgan rules to custom CXX
 operators in parens

Inspired by https://github.com/llvm/llvm-project/pull/164141
---
 .../readability/SimplifyBooleanExprCheck.cpp  | 31 +++++++++++++------
 .../simplify-boolean-expr-demorgan.cpp        | 16 ++++++++++
 2 files changed, 37 insertions(+), 10 deletions(-)

diff --git 
a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp 
b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
index 50ec10ca0cc24..121c327dc3b58 100644
--- a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
@@ -250,6 +250,21 @@ static bool containsDiscardedTokens(const ASTContext 
&Context,
   return false;
 }
 
+static std::optional<bool>
+tryFixCXXOperator(const Expr *E, SmallVectorImpl<FixItHint> &Fixes) {
+  if (const auto *OpCall = dyn_cast<CXXOperatorCallExpr>(E)) {
+    const StringRef NegatedOperator = negatedOperator(OpCall);
+    if (!NegatedOperator.empty()) {
+      if (OpCall->getOperatorLoc().isMacroID())
+        return true;
+      Fixes.push_back(FixItHint::CreateReplacement(OpCall->getOperatorLoc(),
+                                                   NegatedOperator));
+      return false;
+    }
+  }
+  return std::nullopt;
+}
+
 class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
   using Base = RecursiveASTVisitor<Visitor>;
 
@@ -918,19 +933,15 @@ static bool flipDemorganSide(SmallVectorImpl<FixItHint> 
&Fixes,
     return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO);
   // Overloaded comparisons are represented as CXXOperatorCallExpr rather than
   // BinaryOperator, so negate them by replacing their operator location.
-  if (const auto *OpCall = dyn_cast<CXXOperatorCallExpr>(E)) {
-    const StringRef NegatedOperator = negatedOperator(OpCall);
-    if (!NegatedOperator.empty()) {
-      if (OpCall->getOperatorLoc().isMacroID())
-        return true;
-      Fixes.push_back(FixItHint::CreateReplacement(OpCall->getOperatorLoc(),
-                                                   NegatedOperator));
-      return false;
-    }
-  }
+  if (auto Fixed = tryFixCXXOperator(E, Fixes))
+    return *Fixed;
+
   if (const auto *Paren = dyn_cast<ParenExpr>(E)) {
     if (const auto *BinOp = dyn_cast<BinaryOperator>(Paren->getSubExpr()))
       return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO, Paren);
+    // Overloaded comparisons in parentheses, e.g. (T1 < T2).
+    if (auto Fixed = tryFixCXXOperator(Paren->getSubExpr(), Fixes))
+      return *Fixed;
   }
   // Fallback case just insert a logical not operator.
   if (E->getBeginLoc().isMacroID())
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/readability/simplify-boolean-expr-demorgan.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/readability/simplify-boolean-expr-demorgan.cpp
index 4f3660b4a1c46..e194bb4eb1745 100644
--- 
a/clang-tools-extra/test/clang-tidy/checkers/readability/simplify-boolean-expr-demorgan.cpp
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/readability/simplify-boolean-expr-demorgan.cpp
@@ -105,6 +105,22 @@ void foo(bool A1, bool A2, bool A3, bool A4) {
   // CHECK-FIXES: X = A1 && A2 && A3;
   // CHECK-FIXES-NEXT: X = A1 || (A2 && A3);
   // CHECK-FIXES-NEXT: X = A1 && (A2 || A3);
+
+  struct T {
+    bool operator==(const T&) const;
+    bool operator<(const T&) const;
+  };
+
+  T T1, T2;
+  X = !(T1 == T2 && A1 == A2);
+  X = !(T1 < T2 || (A1 || !A2));
+  X = !((T1 < T2) || (A1 || !A2));
+  // CHECK-MESSAGES: :[[@LINE-3]]:7: warning: boolean expression can be 
simplified by DeMorgan's theorem
+  // CHECK-MESSAGES: :[[@LINE-3]]:7: warning: boolean expression can be 
simplified by DeMorgan's theorem
+  // CHECK-MESSAGES: :[[@LINE-3]]:7: warning: boolean expression can be 
simplified by DeMorgan's theorem
+  // CHECK-FIXES: X = T1 != T2 || A1 != A2;
+  // CHECK-FIXES-NEXT: X = T1 >= T2 && !A1 && A2;
+  // CHECK-FIXES-NEXT: X = (T1 >= T2) && !A1 && A2;
 }
 
 // Equality on a user-defined type is an overloaded operator, so its negation

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to