https://github.com/irishrover updated https://github.com/llvm/llvm-project/pull/221432
>From 6cfb63678953c7366c54a179f0f5274b4354bb32 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 c795a1701052a7a6be9baf49d2cbd7513e0b4fce 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..4fde7fe1b6fc5 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
