Skip to content

Commit 9e440a1

Browse files
committed
[clang-tidy] Apply DeMorgan to overloaded operator in the 'readability-simplify-boolean-expr' check
This doesn't check whether the negated operator is defined, following the same behavior in other parts of the code that deal with overloaded operators. Fixes #163128
1 parent 3f9f522 commit 9e440a1

File tree

3 files changed

+78
-25
lines changed

3 files changed

+78
-25
lines changed

clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -809,14 +809,14 @@ void SimplifyBooleanExprCheck::replaceWithAssignment(const ASTContext &Context,
809809
Range, Replacement);
810810
}
811811

812-
/// Swaps a \c BinaryOperator opcode from `&&` to `||` or vice-versa.
812+
/// Swaps \p BO from `&&` to `||` or vice-versa.
813813
static bool flipDemorganOperator(llvm::SmallVectorImpl<FixItHint> &Output,
814-
const BinaryOperator *BO) {
815-
assert(BO->isLogicalOp());
816-
if (BO->getOperatorLoc().isMacroID())
814+
BinaryOperatorKind BO, SourceLocation BOLoc) {
815+
assert(BinaryOperator::isLogicalOp(BO));
816+
if (BOLoc.isMacroID())
817817
return true;
818-
Output.push_back(FixItHint::CreateReplacement(
819-
BO->getOperatorLoc(), BO->getOpcode() == BO_LAnd ? "||" : "&&"));
818+
Output.push_back(
819+
FixItHint::CreateReplacement(BOLoc, BO == BO_LAnd ? "||" : "&&"));
820820
return false;
821821
}
822822

@@ -829,22 +829,24 @@ static bool flipDemorganSide(SmallVectorImpl<FixItHint> &Fixes,
829829
const ASTContext &Ctx, const Expr *E,
830830
std::optional<BinaryOperatorKind> OuterBO);
831831

832-
/// Inverts \p BinOp, Removing \p Parens if they exist and are safe to remove.
833-
/// returns \c true if there is any issue building the Fixes, \c false
834-
/// otherwise.
832+
/// Inverts \p BO, \p LHS, and \p RHS, Removing \p Parens if they exist and are
833+
/// safe to remove. returns \c true if there is any issue building the Fixes, \c
834+
/// false otherwise.
835835
static bool
836836
flipDemorganBinaryOperator(SmallVectorImpl<FixItHint> &Fixes,
837-
const ASTContext &Ctx, const BinaryOperator *BinOp,
837+
const ASTContext &Ctx, BinaryOperatorKind BO,
838+
const Expr *LHS, const Expr *RHS,
839+
SourceRange ExprRange, SourceLocation BOLoc,
838840
std::optional<BinaryOperatorKind> OuterBO,
839841
const ParenExpr *Parens = nullptr) {
840-
switch (BinOp->getOpcode()) {
842+
switch (BO) {
841843
case BO_LAnd:
842844
case BO_LOr: {
843845
// if we have 'a && b' or 'a || b', use demorgan to flip it to '!a || !b'
844846
// or '!a && !b'.
845-
if (flipDemorganOperator(Fixes, BinOp))
847+
if (flipDemorganOperator(Fixes, BO, BOLoc))
846848
return true;
847-
auto NewOp = getDemorganFlippedOperator(BinOp->getOpcode());
849+
auto NewOp = getDemorganFlippedOperator(BO);
848850
if (OuterBO) {
849851
// The inner parens are technically needed in a fix for
850852
// `!(!A1 && !(A2 || A3)) -> (A1 || (A2 && A3))`,
@@ -862,16 +864,16 @@ flipDemorganBinaryOperator(SmallVectorImpl<FixItHint> &Fixes,
862864
}
863865
}
864866
if (*OuterBO == BO_LAnd && NewOp == BO_LOr && !Parens) {
865-
Fixes.push_back(FixItHint::CreateInsertion(BinOp->getBeginLoc(), "("));
867+
Fixes.push_back(FixItHint::CreateInsertion(ExprRange.getBegin(), "("));
866868
Fixes.push_back(FixItHint::CreateInsertion(
867-
Lexer::getLocForEndOfToken(BinOp->getEndLoc(), 0,
869+
Lexer::getLocForEndOfToken(ExprRange.getEnd(), 0,
868870
Ctx.getSourceManager(),
869871
Ctx.getLangOpts()),
870872
")"));
871873
}
872874
}
873-
if (flipDemorganSide(Fixes, Ctx, BinOp->getLHS(), NewOp) ||
874-
flipDemorganSide(Fixes, Ctx, BinOp->getRHS(), NewOp))
875+
if (flipDemorganSide(Fixes, Ctx, LHS, NewOp) ||
876+
flipDemorganSide(Fixes, Ctx, RHS, NewOp))
875877
return true;
876878
return false;
877879
};
@@ -882,12 +884,11 @@ flipDemorganBinaryOperator(SmallVectorImpl<FixItHint> &Fixes,
882884
case BO_EQ:
883885
case BO_NE:
884886
// For comparison operators, just negate the comparison.
885-
if (BinOp->getOperatorLoc().isMacroID())
887+
if (BOLoc.isMacroID())
886888
return true;
887889
Fixes.push_back(FixItHint::CreateReplacement(
888-
BinOp->getOperatorLoc(),
889-
BinaryOperator::getOpcodeStr(
890-
BinaryOperator::negateComparisonOp(BinOp->getOpcode()))));
890+
BOLoc,
891+
BinaryOperator::getOpcodeStr(BinaryOperator::negateComparisonOp(BO))));
891892
return false;
892893
default:
893894
// for any other binary operator, just use logical not and wrap in
@@ -897,11 +898,11 @@ flipDemorganBinaryOperator(SmallVectorImpl<FixItHint> &Fixes,
897898
return true;
898899
Fixes.push_back(FixItHint::CreateInsertion(Parens->getBeginLoc(), "!"));
899900
} else {
900-
if (BinOp->getBeginLoc().isMacroID() || BinOp->getEndLoc().isMacroID())
901+
if (ExprRange.getBegin().isMacroID() || ExprRange.getEnd().isMacroID())
901902
return true;
902-
Fixes.append({FixItHint::CreateInsertion(BinOp->getBeginLoc(), "!("),
903+
Fixes.append({FixItHint::CreateInsertion(ExprRange.getBegin(), "!("),
903904
FixItHint::CreateInsertion(
904-
Lexer::getLocForEndOfToken(BinOp->getEndLoc(), 0,
905+
Lexer::getLocForEndOfToken(ExprRange.getEnd(), 0,
905906
Ctx.getSourceManager(),
906907
Ctx.getLangOpts()),
907908
")")});
@@ -911,6 +912,28 @@ flipDemorganBinaryOperator(SmallVectorImpl<FixItHint> &Fixes,
911912
return false;
912913
}
913914

915+
static bool
916+
flipDemorganBinaryOperator(SmallVectorImpl<FixItHint> &Fixes,
917+
const ASTContext &Ctx, const BinaryOperator *BinOp,
918+
std::optional<BinaryOperatorKind> OuterBO,
919+
const ParenExpr *Parens = nullptr) {
920+
return flipDemorganBinaryOperator(
921+
Fixes, Ctx, BinOp->getOpcode(), BinOp->getLHS(), BinOp->getRHS(),
922+
BinOp->getSourceRange(), BinOp->getOperatorLoc(), OuterBO, Parens);
923+
}
924+
925+
static bool
926+
flipDemorganOverloadedBinaryOperator(SmallVectorImpl<FixItHint> &Fixes,
927+
const ASTContext &Ctx,
928+
const CXXOperatorCallExpr *OpCall,
929+
std::optional<BinaryOperatorKind> OuterBO,
930+
const ParenExpr *Parens = nullptr) {
931+
auto BO = BinaryOperator::getOverloadedOpcode(OpCall->getOperator());
932+
return flipDemorganBinaryOperator(Fixes, Ctx, BO, OpCall->getArg(0),
933+
OpCall->getArg(1), OpCall->getSourceRange(),
934+
OpCall->getOperatorLoc(), OuterBO, Parens);
935+
}
936+
914937
static bool flipDemorganSide(SmallVectorImpl<FixItHint> &Fixes,
915938
const ASTContext &Ctx, const Expr *E,
916939
std::optional<BinaryOperatorKind> OuterBO) {
@@ -930,6 +953,17 @@ static bool flipDemorganSide(SmallVectorImpl<FixItHint> &Fixes,
930953
return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO, Paren);
931954
}
932955
}
956+
if (const auto *OpCall = dyn_cast<CXXOperatorCallExpr>(E);
957+
OpCall && OpCall->isInfixBinaryOp()) {
958+
return flipDemorganOverloadedBinaryOperator(Fixes, Ctx, OpCall, OuterBO);
959+
}
960+
if (const auto *Paren = dyn_cast<ParenExpr>(E)) {
961+
if (const auto *OpCall = dyn_cast<CXXOperatorCallExpr>(Paren->getSubExpr());
962+
OpCall && OpCall->isInfixBinaryOp()) {
963+
return flipDemorganOverloadedBinaryOperator(Fixes, Ctx, OpCall, OuterBO,
964+
Paren);
965+
}
966+
}
933967
// Fallback case just insert a logical not operator.
934968
if (E->getBeginLoc().isMacroID())
935969
return true;
@@ -991,7 +1025,7 @@ bool SimplifyBooleanExprCheck::reportDeMorgan(const ASTContext &Context,
9911025
} else {
9921026
Fixes.push_back(FixItHint::CreateRemoval(Outer->getOperatorLoc()));
9931027
}
994-
if (flipDemorganOperator(Fixes, Inner))
1028+
if (flipDemorganOperator(Fixes, Inner->getOpcode(), Inner->getOperatorLoc()))
9951029
return false;
9961030
if (flipDemorganSide(Fixes, Context, Inner->getLHS(), NewOpcode) ||
9971031
flipDemorganSide(Fixes, Context, Inner->getRHS(), NewOpcode))

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,10 @@ Changes in existing checks
425425
<clang-tidy/checks/readability/uppercase-literal-suffix>` check to recognize
426426
literal suffixes added in C++23 and C23.
427427

428+
- Improved :doc:`readability-simplify-boolean-expr
429+
<clang-tidy/checks/readability/simplify-boolean-expr>` check to handle
430+
overloaded binary operators when applying De Morgan's law.
431+
428432
Removed checks
429433
^^^^^^^^^^^^^^
430434

clang-tools-extra/test/clang-tidy/checkers/readability/simplify-boolean-expr-demorgan.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,19 @@ void foo(bool A1, bool A2, bool A3, bool A4) {
105105
// CHECK-FIXES: X = A1 && A2 && A3;
106106
// CHECK-FIXES-NEXT: X = A1 || (A2 && A3);
107107
// CHECK-FIXES-NEXT: X = A1 && (A2 || A3);
108+
109+
struct T {
110+
bool operator==(const T&) const;
111+
bool operator<(const T&) const;
112+
};
113+
T T1, T2;
114+
X = !(T1 == T2 && A1 == A2);
115+
X = !(T1 < T2 || (A1 || !A2));
116+
X = !((T1 < T2) || (A1 || !A2));
117+
// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: boolean expression can be simplified by DeMorgan's theorem
118+
// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: boolean expression can be simplified by DeMorgan's theorem
119+
// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: boolean expression can be simplified by DeMorgan's theorem
120+
// CHECK-FIXES: X = T1 != T2 || A1 != A2;
121+
// CHECK-FIXES-NEXT: X = T1 >= T2 && !A1 && A2;
122+
// CHECK-FIXES-NEXT: X = (T1 >= T2) && !A1 && A2;
108123
}

0 commit comments

Comments
 (0)