[clang-tools-extra] [clang-tidy] Fix De Morgan for overloaded comparisons (PR #221432)

Zinovy Nis via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 8 10:57:33 PDT 2026


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

>From 8f776ae81ccbcd1614ce2ada2c5eeac75418b048 Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Sat, 5 Sep 2026 13:59:09 +0300
Subject: [PATCH] [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        |  4 ++
 .../simplify-boolean-expr-demorgan.cpp        | 41 +++++++++++++++++++
 3 files changed, 57 insertions(+)

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 b32b8a51e0606..30b1af8e7e2e3 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -240,6 +240,10 @@ infrastructure are described first, followed by tool-specific sections.
   exclusively for overload resolution. Added the {option}`IgnoredTypes`
   option to allow customizing the set of ignored types.
 
+- 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



More information about the cfe-commits mailing list