[clang-tools-extra] [clang-tidy] Fix De Morgan for overloaded comparisons (PR #221432)
Zinovy Nis via cfe-commits
cfe-commits at lists.llvm.org
Sat Sep 19 03:56:00 PDT 2026
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 <zinovy.nis at gmail.com>
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 3a63b6f84eed37..50ec10ca0cc24d 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 3373acbc1b4a10..021f928ff718a3 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 bab9e17a7775b2..4f3660b4a1c468 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 84869444dfeb4793a82d107e9727b837dca353ec Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
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 50ec10ca0cc24d..4fde7fe1b6fc5c 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 4f3660b4a1c468..e194bb4eb1745c 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
More information about the cfe-commits
mailing list