|  | //===-- SimplifyBooleanExprCheck.cpp - clang-tidy -------------------------===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "SimplifyBooleanExprCheck.h" | 
|  | #include "clang/AST/Expr.h" | 
|  | #include "clang/AST/RecursiveASTVisitor.h" | 
|  | #include "clang/Basic/DiagnosticIDs.h" | 
|  | #include "clang/Lex/Lexer.h" | 
|  | #include "llvm/Support/SaveAndRestore.h" | 
|  |  | 
|  | #include <optional> | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | using namespace clang::ast_matchers; | 
|  |  | 
|  | namespace clang::tidy::readability { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | StringRef getText(const ASTContext &Context, SourceRange Range) { | 
|  | return Lexer::getSourceText(CharSourceRange::getTokenRange(Range), | 
|  | Context.getSourceManager(), | 
|  | Context.getLangOpts()); | 
|  | } | 
|  |  | 
|  | template <typename T> StringRef getText(const ASTContext &Context, T &Node) { | 
|  | return getText(Context, Node.getSourceRange()); | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | static constexpr char SimplifyOperatorDiagnostic[] = | 
|  | "redundant boolean literal supplied to boolean operator"; | 
|  | static constexpr char SimplifyConditionDiagnostic[] = | 
|  | "redundant boolean literal in if statement condition"; | 
|  | static constexpr char SimplifyConditionalReturnDiagnostic[] = | 
|  | "redundant boolean literal in conditional return statement"; | 
|  |  | 
|  | static bool needsParensAfterUnaryNegation(const Expr *E) { | 
|  | E = E->IgnoreImpCasts(); | 
|  | if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E)) | 
|  | return true; | 
|  |  | 
|  | if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E)) | 
|  | return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call && | 
|  | Op->getOperator() != OO_Subscript; | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = { | 
|  | {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}}; | 
|  |  | 
|  | static StringRef negatedOperator(const BinaryOperator *BinOp) { | 
|  | const BinaryOperatorKind Opcode = BinOp->getOpcode(); | 
|  | for (auto NegatableOp : Opposites) { | 
|  | if (Opcode == NegatableOp.first) | 
|  | return BinaryOperator::getOpcodeStr(NegatableOp.second); | 
|  | if (Opcode == NegatableOp.second) | 
|  | return BinaryOperator::getOpcodeStr(NegatableOp.first); | 
|  | } | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | static std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = { | 
|  | {OO_EqualEqual, "=="},   {OO_ExclaimEqual, "!="}, {OO_Less, "<"}, | 
|  | {OO_GreaterEqual, ">="}, {OO_Greater, ">"},       {OO_LessEqual, "<="}}; | 
|  |  | 
|  | static StringRef getOperatorName(OverloadedOperatorKind OpKind) { | 
|  | for (auto Name : OperatorNames) { | 
|  | if (Name.first == OpKind) | 
|  | return Name.second; | 
|  | } | 
|  |  | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | static std::pair<OverloadedOperatorKind, OverloadedOperatorKind> | 
|  | OppositeOverloads[] = {{OO_EqualEqual, OO_ExclaimEqual}, | 
|  | {OO_Less, OO_GreaterEqual}, | 
|  | {OO_Greater, OO_LessEqual}}; | 
|  |  | 
|  | static StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) { | 
|  | const OverloadedOperatorKind Opcode = OpCall->getOperator(); | 
|  | for (auto NegatableOp : OppositeOverloads) { | 
|  | if (Opcode == NegatableOp.first) | 
|  | return getOperatorName(NegatableOp.second); | 
|  | if (Opcode == NegatableOp.second) | 
|  | return getOperatorName(NegatableOp.first); | 
|  | } | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | static std::string asBool(StringRef Text, bool NeedsStaticCast) { | 
|  | if (NeedsStaticCast) | 
|  | return ("static_cast<bool>(" + Text + ")").str(); | 
|  |  | 
|  | return std::string(Text); | 
|  | } | 
|  |  | 
|  | static bool needsNullPtrComparison(const Expr *E) { | 
|  | if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) | 
|  | return ImpCast->getCastKind() == CK_PointerToBoolean || | 
|  | ImpCast->getCastKind() == CK_MemberPointerToBoolean; | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool needsZeroComparison(const Expr *E) { | 
|  | if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) | 
|  | return ImpCast->getCastKind() == CK_IntegralToBoolean; | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool needsStaticCast(const Expr *E) { | 
|  | if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) { | 
|  | if (ImpCast->getCastKind() == CK_UserDefinedConversion && | 
|  | ImpCast->getSubExpr()->getType()->isBooleanType()) { | 
|  | if (const auto *MemCall = | 
|  | dyn_cast<CXXMemberCallExpr>(ImpCast->getSubExpr())) { | 
|  | if (const auto *MemDecl = | 
|  | dyn_cast<CXXConversionDecl>(MemCall->getMethodDecl())) { | 
|  | if (MemDecl->isExplicit()) | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | E = E->IgnoreImpCasts(); | 
|  | return !E->getType()->isBooleanType(); | 
|  | } | 
|  |  | 
|  | static std::string compareExpressionToConstant(const ASTContext &Context, | 
|  | const Expr *E, bool Negated, | 
|  | const char *Constant) { | 
|  | E = E->IgnoreImpCasts(); | 
|  | const std::string ExprText = | 
|  | (isa<BinaryOperator>(E) ? ("(" + getText(Context, *E) + ")") | 
|  | : getText(Context, *E)) | 
|  | .str(); | 
|  | return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant; | 
|  | } | 
|  |  | 
|  | static std::string compareExpressionToNullPtr(const ASTContext &Context, | 
|  | const Expr *E, bool Negated) { | 
|  | const char *NullPtr = Context.getLangOpts().CPlusPlus11 ? "nullptr" : "NULL"; | 
|  | return compareExpressionToConstant(Context, E, Negated, NullPtr); | 
|  | } | 
|  |  | 
|  | static std::string compareExpressionToZero(const ASTContext &Context, | 
|  | const Expr *E, bool Negated) { | 
|  | return compareExpressionToConstant(Context, E, Negated, "0"); | 
|  | } | 
|  |  | 
|  | static std::string replacementExpression(const ASTContext &Context, | 
|  | bool Negated, const Expr *E) { | 
|  | E = E->IgnoreParenBaseCasts(); | 
|  | if (const auto *EC = dyn_cast<ExprWithCleanups>(E)) | 
|  | E = EC->getSubExpr(); | 
|  |  | 
|  | const bool NeedsStaticCast = needsStaticCast(E); | 
|  | if (Negated) { | 
|  | if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) { | 
|  | if (UnOp->getOpcode() == UO_LNot) { | 
|  | if (needsNullPtrComparison(UnOp->getSubExpr())) | 
|  | return compareExpressionToNullPtr(Context, UnOp->getSubExpr(), true); | 
|  |  | 
|  | if (needsZeroComparison(UnOp->getSubExpr())) | 
|  | return compareExpressionToZero(Context, UnOp->getSubExpr(), true); | 
|  |  | 
|  | return replacementExpression(Context, false, UnOp->getSubExpr()); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (needsNullPtrComparison(E)) | 
|  | return compareExpressionToNullPtr(Context, E, false); | 
|  |  | 
|  | if (needsZeroComparison(E)) | 
|  | return compareExpressionToZero(Context, E, false); | 
|  |  | 
|  | StringRef NegatedOperator; | 
|  | const Expr *LHS = nullptr; | 
|  | const Expr *RHS = nullptr; | 
|  | if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) { | 
|  | NegatedOperator = negatedOperator(BinOp); | 
|  | LHS = BinOp->getLHS(); | 
|  | RHS = BinOp->getRHS(); | 
|  | } else if (const auto *OpExpr = dyn_cast<CXXOperatorCallExpr>(E)) { | 
|  | if (OpExpr->getNumArgs() == 2) { | 
|  | NegatedOperator = negatedOperator(OpExpr); | 
|  | LHS = OpExpr->getArg(0); | 
|  | RHS = OpExpr->getArg(1); | 
|  | } | 
|  | } | 
|  | if (!NegatedOperator.empty() && LHS && RHS) | 
|  | return (asBool((getText(Context, *LHS) + " " + NegatedOperator + " " + | 
|  | getText(Context, *RHS)) | 
|  | .str(), | 
|  | NeedsStaticCast)); | 
|  |  | 
|  | StringRef Text = getText(Context, *E); | 
|  | if (!NeedsStaticCast && needsParensAfterUnaryNegation(E)) | 
|  | return ("!(" + Text + ")").str(); | 
|  |  | 
|  | if (needsNullPtrComparison(E)) | 
|  | return compareExpressionToNullPtr(Context, E, false); | 
|  |  | 
|  | if (needsZeroComparison(E)) | 
|  | return compareExpressionToZero(Context, E, false); | 
|  |  | 
|  | return ("!" + asBool(Text, NeedsStaticCast)); | 
|  | } | 
|  |  | 
|  | if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) { | 
|  | if (UnOp->getOpcode() == UO_LNot) { | 
|  | if (needsNullPtrComparison(UnOp->getSubExpr())) | 
|  | return compareExpressionToNullPtr(Context, UnOp->getSubExpr(), false); | 
|  |  | 
|  | if (needsZeroComparison(UnOp->getSubExpr())) | 
|  | return compareExpressionToZero(Context, UnOp->getSubExpr(), false); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (needsNullPtrComparison(E)) | 
|  | return compareExpressionToNullPtr(Context, E, true); | 
|  |  | 
|  | if (needsZeroComparison(E)) | 
|  | return compareExpressionToZero(Context, E, true); | 
|  |  | 
|  | return asBool(getText(Context, *E), NeedsStaticCast); | 
|  | } | 
|  |  | 
|  | static bool containsDiscardedTokens(const ASTContext &Context, | 
|  | CharSourceRange CharRange) { | 
|  | std::string ReplacementText = | 
|  | Lexer::getSourceText(CharRange, Context.getSourceManager(), | 
|  | Context.getLangOpts()) | 
|  | .str(); | 
|  | Lexer Lex(CharRange.getBegin(), Context.getLangOpts(), ReplacementText.data(), | 
|  | ReplacementText.data(), | 
|  | ReplacementText.data() + ReplacementText.size()); | 
|  | Lex.SetCommentRetentionState(true); | 
|  |  | 
|  | Token Tok; | 
|  | while (!Lex.LexFromRawLexer(Tok)) { | 
|  | if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash)) | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> { | 
|  | using Base = RecursiveASTVisitor<Visitor>; | 
|  |  | 
|  | public: | 
|  | Visitor(SimplifyBooleanExprCheck *Check, ASTContext &Context) | 
|  | : Check(Check), Context(Context) {} | 
|  |  | 
|  | bool traverse() { return TraverseAST(Context); } | 
|  |  | 
|  | static bool shouldIgnore(Stmt *S) { | 
|  | switch (S->getStmtClass()) { | 
|  | case Stmt::ImplicitCastExprClass: | 
|  | case Stmt::MaterializeTemporaryExprClass: | 
|  | case Stmt::CXXBindTemporaryExprClass: | 
|  | return true; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool dataTraverseStmtPre(Stmt *S) { | 
|  | if (!S) { | 
|  | return true; | 
|  | } | 
|  | if (Check->canBeBypassed(S)) | 
|  | return false; | 
|  | if (!shouldIgnore(S)) | 
|  | StmtStack.push_back(S); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool dataTraverseStmtPost(Stmt *S) { | 
|  | if (S && !shouldIgnore(S)) { | 
|  | assert(StmtStack.back() == S); | 
|  | StmtStack.pop_back(); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool VisitBinaryOperator(const BinaryOperator *Op) const { | 
|  | Check->reportBinOp(Context, Op); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Extracts a bool if an expression is (true|false|!true|!false); | 
|  | static std::optional<bool> getAsBoolLiteral(const Expr *E, bool FilterMacro) { | 
|  | if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(E)) { | 
|  | if (FilterMacro && Bool->getBeginLoc().isMacroID()) | 
|  | return std::nullopt; | 
|  | return Bool->getValue(); | 
|  | } | 
|  | if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E)) { | 
|  | if (FilterMacro && UnaryOp->getBeginLoc().isMacroID()) | 
|  | return std::nullopt; | 
|  | if (UnaryOp->getOpcode() == UO_LNot) | 
|  | if (std::optional<bool> Res = getAsBoolLiteral( | 
|  | UnaryOp->getSubExpr()->IgnoreImplicit(), FilterMacro)) | 
|  | return !*Res; | 
|  | } | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | template <typename Node> struct NodeAndBool { | 
|  | const Node *Item = nullptr; | 
|  | bool Bool = false; | 
|  |  | 
|  | operator bool() const { return Item != nullptr; } | 
|  | }; | 
|  |  | 
|  | using ExprAndBool = NodeAndBool<Expr>; | 
|  | using DeclAndBool = NodeAndBool<Decl>; | 
|  |  | 
|  | /// Detect's return (true|false|!true|!false); | 
|  | static ExprAndBool parseReturnLiteralBool(const Stmt *S) { | 
|  | const auto *RS = dyn_cast<ReturnStmt>(S); | 
|  | if (!RS || !RS->getRetValue()) | 
|  | return {}; | 
|  | if (std::optional<bool> Ret = | 
|  | getAsBoolLiteral(RS->getRetValue()->IgnoreImplicit(), false)) { | 
|  | return {RS->getRetValue(), *Ret}; | 
|  | } | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | /// If \p S is not a \c CompoundStmt, applies F on \p S, otherwise if there is | 
|  | /// only 1 statement in the \c CompoundStmt, applies F on that single | 
|  | /// statement. | 
|  | template <typename Functor> | 
|  | static auto checkSingleStatement(Stmt *S, Functor F) -> decltype(F(S)) { | 
|  | if (auto *CS = dyn_cast<CompoundStmt>(S)) { | 
|  | if (CS->size() == 1) | 
|  | return F(CS->body_front()); | 
|  | return {}; | 
|  | } | 
|  | return F(S); | 
|  | } | 
|  |  | 
|  | Stmt *parent() const { | 
|  | return StmtStack.size() < 2 ? nullptr : StmtStack[StmtStack.size() - 2]; | 
|  | } | 
|  |  | 
|  | bool VisitIfStmt(IfStmt *If) { | 
|  | // Skip any if's that have a condition var or an init statement, or are | 
|  | // "if consteval" statements. | 
|  | if (If->hasInitStorage() || If->hasVarStorage() || If->isConsteval()) | 
|  | return true; | 
|  | /* | 
|  | * if (true) ThenStmt(); -> ThenStmt(); | 
|  | * if (false) ThenStmt(); -> <Empty>; | 
|  | * if (false) ThenStmt(); else ElseStmt() -> ElseStmt(); | 
|  | */ | 
|  | Expr *Cond = If->getCond()->IgnoreImplicit(); | 
|  | if (std::optional<bool> Bool = getAsBoolLiteral(Cond, true)) { | 
|  | if (*Bool) | 
|  | Check->replaceWithThenStatement(Context, If, Cond); | 
|  | else | 
|  | Check->replaceWithElseStatement(Context, If, Cond); | 
|  | } | 
|  |  | 
|  | if (If->getElse()) { | 
|  | /* | 
|  | * if (Cond) return true; else return false; -> return Cond; | 
|  | * if (Cond) return false; else return true; -> return !Cond; | 
|  | */ | 
|  | if (ExprAndBool ThenReturnBool = | 
|  | checkSingleStatement(If->getThen(), parseReturnLiteralBool)) { | 
|  | ExprAndBool ElseReturnBool = | 
|  | checkSingleStatement(If->getElse(), parseReturnLiteralBool); | 
|  | if (ElseReturnBool && ThenReturnBool.Bool != ElseReturnBool.Bool) { | 
|  | if (Check->ChainedConditionalReturn || | 
|  | !isa_and_nonnull<IfStmt>(parent())) { | 
|  | Check->replaceWithReturnCondition(Context, If, ThenReturnBool.Item, | 
|  | ElseReturnBool.Bool); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | /* | 
|  | * if (Cond) A = true; else A = false; -> A = Cond; | 
|  | * if (Cond) A = false; else A = true; -> A = !Cond; | 
|  | */ | 
|  | Expr *Var = nullptr; | 
|  | SourceLocation Loc; | 
|  | auto VarBoolAssignmentMatcher = [&Var, | 
|  | &Loc](const Stmt *S) -> DeclAndBool { | 
|  | const auto *BO = dyn_cast<BinaryOperator>(S); | 
|  | if (!BO || BO->getOpcode() != BO_Assign) | 
|  | return {}; | 
|  | std::optional<bool> RightasBool = | 
|  | getAsBoolLiteral(BO->getRHS()->IgnoreImplicit(), false); | 
|  | if (!RightasBool) | 
|  | return {}; | 
|  | Expr *IgnImp = BO->getLHS()->IgnoreImplicit(); | 
|  | if (!Var) { | 
|  | // We only need to track these for the Then branch. | 
|  | Loc = BO->getRHS()->getBeginLoc(); | 
|  | Var = IgnImp; | 
|  | } | 
|  | if (auto *DRE = dyn_cast<DeclRefExpr>(IgnImp)) | 
|  | return {DRE->getDecl(), *RightasBool}; | 
|  | if (auto *ME = dyn_cast<MemberExpr>(IgnImp)) | 
|  | return {ME->getMemberDecl(), *RightasBool}; | 
|  | return {}; | 
|  | }; | 
|  | if (DeclAndBool ThenAssignment = | 
|  | checkSingleStatement(If->getThen(), VarBoolAssignmentMatcher)) { | 
|  | DeclAndBool ElseAssignment = | 
|  | checkSingleStatement(If->getElse(), VarBoolAssignmentMatcher); | 
|  | if (ElseAssignment.Item == ThenAssignment.Item && | 
|  | ElseAssignment.Bool != ThenAssignment.Bool) { | 
|  | if (Check->ChainedConditionalAssignment || | 
|  | !isa_and_nonnull<IfStmt>(parent())) { | 
|  | Check->replaceWithAssignment(Context, If, Var, Loc, | 
|  | ElseAssignment.Bool); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool VisitConditionalOperator(ConditionalOperator *Cond) { | 
|  | /* | 
|  | * Condition ? true : false; -> Condition | 
|  | * Condition ? false : true; -> !Condition; | 
|  | */ | 
|  | if (std::optional<bool> Then = | 
|  | getAsBoolLiteral(Cond->getTrueExpr()->IgnoreImplicit(), false)) { | 
|  | if (std::optional<bool> Else = | 
|  | getAsBoolLiteral(Cond->getFalseExpr()->IgnoreImplicit(), false)) { | 
|  | if (*Then != *Else) | 
|  | Check->replaceWithCondition(Context, Cond, *Else); | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool VisitCompoundStmt(CompoundStmt *CS) { | 
|  | if (CS->size() < 2) | 
|  | return true; | 
|  | bool CurIf = false, PrevIf = false; | 
|  | for (auto First = CS->body_begin(), Second = std::next(First), | 
|  | End = CS->body_end(); | 
|  | Second != End; ++Second, ++First) { | 
|  | PrevIf = CurIf; | 
|  | CurIf = isa<IfStmt>(*First); | 
|  | ExprAndBool TrailingReturnBool = parseReturnLiteralBool(*Second); | 
|  | if (!TrailingReturnBool) | 
|  | continue; | 
|  |  | 
|  | if (CurIf) { | 
|  | /* | 
|  | * if (Cond) return true; return false; -> return Cond; | 
|  | * if (Cond) return false; return true; -> return !Cond; | 
|  | */ | 
|  | auto *If = cast<IfStmt>(*First); | 
|  | if (!If->hasInitStorage() && !If->hasVarStorage() && | 
|  | !If->isConsteval()) { | 
|  | ExprAndBool ThenReturnBool = | 
|  | checkSingleStatement(If->getThen(), parseReturnLiteralBool); | 
|  | if (ThenReturnBool && | 
|  | ThenReturnBool.Bool != TrailingReturnBool.Bool) { | 
|  | if ((Check->ChainedConditionalReturn || !PrevIf) && | 
|  | If->getElse() == nullptr) { | 
|  | Check->replaceCompoundReturnWithCondition( | 
|  | Context, cast<ReturnStmt>(*Second), TrailingReturnBool.Bool, | 
|  | If, ThenReturnBool.Item); | 
|  | } | 
|  | } | 
|  | } | 
|  | } else if (isa<LabelStmt, CaseStmt, DefaultStmt>(*First)) { | 
|  | /* | 
|  | * (case X|label_X|default): if (Cond) return BoolLiteral; | 
|  | *                           return !BoolLiteral | 
|  | */ | 
|  | Stmt *SubStmt = | 
|  | isa<LabelStmt>(*First)  ? cast<LabelStmt>(*First)->getSubStmt() | 
|  | : isa<CaseStmt>(*First) ? cast<CaseStmt>(*First)->getSubStmt() | 
|  | : cast<DefaultStmt>(*First)->getSubStmt(); | 
|  | auto *SubIf = dyn_cast<IfStmt>(SubStmt); | 
|  | if (SubIf && !SubIf->getElse() && !SubIf->hasInitStorage() && | 
|  | !SubIf->hasVarStorage() && !SubIf->isConsteval()) { | 
|  | ExprAndBool ThenReturnBool = | 
|  | checkSingleStatement(SubIf->getThen(), parseReturnLiteralBool); | 
|  | if (ThenReturnBool && | 
|  | ThenReturnBool.Bool != TrailingReturnBool.Bool) { | 
|  | Check->replaceCompoundReturnWithCondition( | 
|  | Context, cast<ReturnStmt>(*Second), TrailingReturnBool.Bool, | 
|  | SubIf, ThenReturnBool.Item); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool isExpectedUnaryLNot(const Expr *E) { | 
|  | return !Check->canBeBypassed(E) && isa<UnaryOperator>(E) && | 
|  | cast<UnaryOperator>(E)->getOpcode() == UO_LNot; | 
|  | } | 
|  |  | 
|  | bool isExpectedBinaryOp(const Expr *E) { | 
|  | const auto *BinaryOp = dyn_cast<BinaryOperator>(E); | 
|  | return !Check->canBeBypassed(E) && BinaryOp && BinaryOp->isLogicalOp() && | 
|  | BinaryOp->getType()->isBooleanType(); | 
|  | } | 
|  |  | 
|  | template <typename Functor> | 
|  | static bool checkEitherSide(const BinaryOperator *BO, Functor Func) { | 
|  | return Func(BO->getLHS()) || Func(BO->getRHS()); | 
|  | } | 
|  |  | 
|  | bool nestedDemorgan(const Expr *E, unsigned NestingLevel) { | 
|  | const auto *BO = dyn_cast<BinaryOperator>(E->IgnoreUnlessSpelledInSource()); | 
|  | if (!BO) | 
|  | return false; | 
|  | if (!BO->getType()->isBooleanType()) | 
|  | return false; | 
|  | switch (BO->getOpcode()) { | 
|  | case BO_LT: | 
|  | case BO_GT: | 
|  | case BO_LE: | 
|  | case BO_GE: | 
|  | case BO_EQ: | 
|  | case BO_NE: | 
|  | return true; | 
|  | case BO_LAnd: | 
|  | case BO_LOr: | 
|  | return checkEitherSide( | 
|  | BO, | 
|  | [this](const Expr *E) { return isExpectedUnaryLNot(E); }) || | 
|  | (NestingLevel && | 
|  | checkEitherSide(BO, [this, NestingLevel](const Expr *E) { | 
|  | return nestedDemorgan(E, NestingLevel - 1); | 
|  | })); | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool TraverseUnaryOperator(UnaryOperator *Op) { | 
|  | if (!Check->SimplifyDeMorgan || Op->getOpcode() != UO_LNot) | 
|  | return Base::TraverseUnaryOperator(Op); | 
|  | const Expr *SubImp = Op->getSubExpr()->IgnoreImplicit(); | 
|  | const auto *Parens = dyn_cast<ParenExpr>(SubImp); | 
|  | const Expr *SubExpr = | 
|  | Parens ? Parens->getSubExpr()->IgnoreImplicit() : SubImp; | 
|  | if (!isExpectedBinaryOp(SubExpr)) | 
|  | return Base::TraverseUnaryOperator(Op); | 
|  | const auto *BinaryOp = cast<BinaryOperator>(SubExpr); | 
|  | if (Check->SimplifyDeMorganRelaxed || | 
|  | checkEitherSide( | 
|  | BinaryOp, | 
|  | [this](const Expr *E) { return isExpectedUnaryLNot(E); }) || | 
|  | checkEitherSide( | 
|  | BinaryOp, [this](const Expr *E) { return nestedDemorgan(E, 1); })) { | 
|  | if (Check->reportDeMorgan(Context, Op, BinaryOp, !IsProcessing, parent(), | 
|  | Parens) && | 
|  | !Check->areDiagsSelfContained()) { | 
|  | llvm::SaveAndRestore RAII(IsProcessing, true); | 
|  | return Base::TraverseUnaryOperator(Op); | 
|  | } | 
|  | } | 
|  | return Base::TraverseUnaryOperator(Op); | 
|  | } | 
|  |  | 
|  | private: | 
|  | bool IsProcessing = false; | 
|  | SimplifyBooleanExprCheck *Check; | 
|  | SmallVector<Stmt *, 32> StmtStack; | 
|  | ASTContext &Context; | 
|  | }; | 
|  |  | 
|  | SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name, | 
|  | ClangTidyContext *Context) | 
|  | : ClangTidyCheck(Name, Context), | 
|  | IgnoreMacros(Options.get("IgnoreMacros", false)), | 
|  | ChainedConditionalReturn(Options.get("ChainedConditionalReturn", false)), | 
|  | ChainedConditionalAssignment( | 
|  | Options.get("ChainedConditionalAssignment", false)), | 
|  | SimplifyDeMorgan(Options.get("SimplifyDeMorgan", true)), | 
|  | SimplifyDeMorganRelaxed(Options.get("SimplifyDeMorganRelaxed", false)) { | 
|  | if (SimplifyDeMorganRelaxed && !SimplifyDeMorgan) | 
|  | configurationDiag("%0: 'SimplifyDeMorganRelaxed' cannot be enabled " | 
|  | "without 'SimplifyDeMorgan' enabled") | 
|  | << Name; | 
|  | } | 
|  |  | 
|  | static bool containsBoolLiteral(const Expr *E) { | 
|  | if (!E) | 
|  | return false; | 
|  | E = E->IgnoreParenImpCasts(); | 
|  | if (isa<CXXBoolLiteralExpr>(E)) | 
|  | return true; | 
|  | if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) | 
|  | return containsBoolLiteral(BinOp->getLHS()) || | 
|  | containsBoolLiteral(BinOp->getRHS()); | 
|  | if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E)) | 
|  | return containsBoolLiteral(UnaryOp->getSubExpr()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void SimplifyBooleanExprCheck::reportBinOp(const ASTContext &Context, | 
|  | const BinaryOperator *Op) { | 
|  | const auto *LHS = Op->getLHS()->IgnoreParenImpCasts(); | 
|  | const auto *RHS = Op->getRHS()->IgnoreParenImpCasts(); | 
|  |  | 
|  | const CXXBoolLiteralExpr *Bool = nullptr; | 
|  | const Expr *Other = nullptr; | 
|  | if ((Bool = dyn_cast<CXXBoolLiteralExpr>(LHS)) != nullptr) | 
|  | Other = RHS; | 
|  | else if ((Bool = dyn_cast<CXXBoolLiteralExpr>(RHS)) != nullptr) | 
|  | Other = LHS; | 
|  | else | 
|  | return; | 
|  |  | 
|  | if (Bool->getBeginLoc().isMacroID()) | 
|  | return; | 
|  |  | 
|  | // FIXME: why do we need this? | 
|  | if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other)) | 
|  | return; | 
|  |  | 
|  | bool BoolValue = Bool->getValue(); | 
|  |  | 
|  | auto ReplaceWithExpression = [this, &Context, LHS, RHS, | 
|  | Bool](const Expr *ReplaceWith, bool Negated) { | 
|  | std::string Replacement = | 
|  | replacementExpression(Context, Negated, ReplaceWith); | 
|  | SourceRange Range(LHS->getBeginLoc(), RHS->getEndLoc()); | 
|  | issueDiag(Context, Bool->getBeginLoc(), SimplifyOperatorDiagnostic, Range, | 
|  | Replacement); | 
|  | }; | 
|  |  | 
|  | switch (Op->getOpcode()) { | 
|  | case BO_LAnd: | 
|  | if (BoolValue) | 
|  | // expr && true -> expr | 
|  | ReplaceWithExpression(Other, /*Negated=*/false); | 
|  | else | 
|  | // expr && false -> false | 
|  | ReplaceWithExpression(Bool, /*Negated=*/false); | 
|  | break; | 
|  | case BO_LOr: | 
|  | if (BoolValue) | 
|  | // expr || true -> true | 
|  | ReplaceWithExpression(Bool, /*Negated=*/false); | 
|  | else | 
|  | // expr || false -> expr | 
|  | ReplaceWithExpression(Other, /*Negated=*/false); | 
|  | break; | 
|  | case BO_EQ: | 
|  | // expr == true -> expr, expr == false -> !expr | 
|  | ReplaceWithExpression(Other, /*Negated=*/!BoolValue); | 
|  | break; | 
|  | case BO_NE: | 
|  | // expr != true -> !expr, expr != false -> expr | 
|  | ReplaceWithExpression(Other, /*Negated=*/BoolValue); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | 
|  | Options.store(Opts, "IgnoreMacros", IgnoreMacros); | 
|  | Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn); | 
|  | Options.store(Opts, "ChainedConditionalAssignment", | 
|  | ChainedConditionalAssignment); | 
|  | Options.store(Opts, "SimplifyDeMorgan", SimplifyDeMorgan); | 
|  | Options.store(Opts, "SimplifyDeMorganRelaxed", SimplifyDeMorganRelaxed); | 
|  | } | 
|  |  | 
|  | void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) { | 
|  | Finder->addMatcher(translationUnitDecl(), this); | 
|  | } | 
|  |  | 
|  | void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) { | 
|  | Visitor(this, *Result.Context).traverse(); | 
|  | } | 
|  |  | 
|  | bool SimplifyBooleanExprCheck::canBeBypassed(const Stmt *S) const { | 
|  | return IgnoreMacros && S->getBeginLoc().isMacroID(); | 
|  | } | 
|  |  | 
|  | /// @brief return true when replacement created. | 
|  | bool SimplifyBooleanExprCheck::issueDiag(const ASTContext &Context, | 
|  | SourceLocation Loc, | 
|  | StringRef Description, | 
|  | SourceRange ReplacementRange, | 
|  | StringRef Replacement) { | 
|  | CharSourceRange CharRange = | 
|  | Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange), | 
|  | Context.getSourceManager(), getLangOpts()); | 
|  |  | 
|  | DiagnosticBuilder Diag = diag(Loc, Description); | 
|  | const bool HasReplacement = !containsDiscardedTokens(Context, CharRange); | 
|  | if (HasReplacement) | 
|  | Diag << FixItHint::CreateReplacement(CharRange, Replacement); | 
|  | return HasReplacement; | 
|  | } | 
|  |  | 
|  | void SimplifyBooleanExprCheck::replaceWithThenStatement( | 
|  | const ASTContext &Context, const IfStmt *IfStatement, | 
|  | const Expr *BoolLiteral) { | 
|  | issueDiag(Context, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic, | 
|  | IfStatement->getSourceRange(), | 
|  | getText(Context, *IfStatement->getThen())); | 
|  | } | 
|  |  | 
|  | void SimplifyBooleanExprCheck::replaceWithElseStatement( | 
|  | const ASTContext &Context, const IfStmt *IfStatement, | 
|  | const Expr *BoolLiteral) { | 
|  | const Stmt *ElseStatement = IfStatement->getElse(); | 
|  | issueDiag(Context, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic, | 
|  | IfStatement->getSourceRange(), | 
|  | ElseStatement ? getText(Context, *ElseStatement) : ""); | 
|  | } | 
|  |  | 
|  | void SimplifyBooleanExprCheck::replaceWithCondition( | 
|  | const ASTContext &Context, const ConditionalOperator *Ternary, | 
|  | bool Negated) { | 
|  | std::string Replacement = | 
|  | replacementExpression(Context, Negated, Ternary->getCond()); | 
|  | issueDiag(Context, Ternary->getTrueExpr()->getBeginLoc(), | 
|  | "redundant boolean literal in ternary expression result", | 
|  | Ternary->getSourceRange(), Replacement); | 
|  | } | 
|  |  | 
|  | void SimplifyBooleanExprCheck::replaceWithReturnCondition( | 
|  | const ASTContext &Context, const IfStmt *If, const Expr *BoolLiteral, | 
|  | bool Negated) { | 
|  | StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : ""; | 
|  | std::string Condition = | 
|  | replacementExpression(Context, Negated, If->getCond()); | 
|  | std::string Replacement = ("return " + Condition + Terminator).str(); | 
|  | SourceLocation Start = BoolLiteral->getBeginLoc(); | 
|  |  | 
|  | const bool HasReplacement = | 
|  | issueDiag(Context, Start, SimplifyConditionalReturnDiagnostic, | 
|  | If->getSourceRange(), Replacement); | 
|  |  | 
|  | if (!HasReplacement) { | 
|  | const SourceRange ConditionRange = If->getCond()->getSourceRange(); | 
|  | if (ConditionRange.isValid()) | 
|  | diag(ConditionRange.getBegin(), "conditions that can be simplified", | 
|  | DiagnosticIDs::Note) | 
|  | << ConditionRange; | 
|  | } | 
|  | } | 
|  |  | 
|  | void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition( | 
|  | const ASTContext &Context, const ReturnStmt *Ret, bool Negated, | 
|  | const IfStmt *If, const Expr *ThenReturn) { | 
|  | const std::string Replacement = | 
|  | "return " + replacementExpression(Context, Negated, If->getCond()); | 
|  |  | 
|  | const bool HasReplacement = issueDiag( | 
|  | Context, ThenReturn->getBeginLoc(), SimplifyConditionalReturnDiagnostic, | 
|  | SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement); | 
|  |  | 
|  | if (!HasReplacement) { | 
|  | const SourceRange ConditionRange = If->getCond()->getSourceRange(); | 
|  | if (ConditionRange.isValid()) | 
|  | diag(ConditionRange.getBegin(), "conditions that can be simplified", | 
|  | DiagnosticIDs::Note) | 
|  | << ConditionRange; | 
|  | const SourceRange ReturnRange = Ret->getSourceRange(); | 
|  | if (ReturnRange.isValid()) | 
|  | diag(ReturnRange.getBegin(), "return statement that can be simplified", | 
|  | DiagnosticIDs::Note) | 
|  | << ReturnRange; | 
|  | } | 
|  | } | 
|  |  | 
|  | void SimplifyBooleanExprCheck::replaceWithAssignment(const ASTContext &Context, | 
|  | const IfStmt *IfAssign, | 
|  | const Expr *Var, | 
|  | SourceLocation Loc, | 
|  | bool Negated) { | 
|  | SourceRange Range = IfAssign->getSourceRange(); | 
|  | StringRef VariableName = getText(Context, *Var); | 
|  | StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : ""; | 
|  | std::string Condition = | 
|  | replacementExpression(Context, Negated, IfAssign->getCond()); | 
|  | std::string Replacement = | 
|  | (VariableName + " = " + Condition + Terminator).str(); | 
|  | issueDiag(Context, Loc, "redundant boolean literal in conditional assignment", | 
|  | Range, Replacement); | 
|  | } | 
|  |  | 
|  | /// Swaps a \c BinaryOperator opcode from `&&` to `||` or vice-versa. | 
|  | static bool flipDemorganOperator(llvm::SmallVectorImpl<FixItHint> &Output, | 
|  | const BinaryOperator *BO) { | 
|  | assert(BO->isLogicalOp()); | 
|  | if (BO->getOperatorLoc().isMacroID()) | 
|  | return true; | 
|  | Output.push_back(FixItHint::CreateReplacement( | 
|  | BO->getOperatorLoc(), BO->getOpcode() == BO_LAnd ? "||" : "&&")); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static BinaryOperatorKind getDemorganFlippedOperator(BinaryOperatorKind BO) { | 
|  | assert(BinaryOperator::isLogicalOp(BO)); | 
|  | return BO == BO_LAnd ? BO_LOr : BO_LAnd; | 
|  | } | 
|  |  | 
|  | static bool flipDemorganSide(SmallVectorImpl<FixItHint> &Fixes, | 
|  | const ASTContext &Ctx, const Expr *E, | 
|  | std::optional<BinaryOperatorKind> OuterBO); | 
|  |  | 
|  | /// Inverts \p BinOp, Removing \p Parens if they exist and are safe to remove. | 
|  | /// returns \c true if there is any issue building the Fixes, \c false | 
|  | /// otherwise. | 
|  | static bool | 
|  | flipDemorganBinaryOperator(SmallVectorImpl<FixItHint> &Fixes, | 
|  | const ASTContext &Ctx, const BinaryOperator *BinOp, | 
|  | std::optional<BinaryOperatorKind> OuterBO, | 
|  | const ParenExpr *Parens = nullptr) { | 
|  | switch (BinOp->getOpcode()) { | 
|  | case BO_LAnd: | 
|  | case BO_LOr: { | 
|  | // if we have 'a && b' or 'a || b', use demorgan to flip it to '!a || !b' | 
|  | // or '!a && !b'. | 
|  | if (flipDemorganOperator(Fixes, BinOp)) | 
|  | return true; | 
|  | auto NewOp = getDemorganFlippedOperator(BinOp->getOpcode()); | 
|  | if (OuterBO) { | 
|  | // The inner parens are technically needed in a fix for | 
|  | // `!(!A1 && !(A2 || A3)) -> (A1 || (A2 && A3))`, | 
|  | // however this would trip the LogicalOpParentheses warning. | 
|  | // FIXME: Make this user configurable or detect if that warning is | 
|  | // enabled. | 
|  | constexpr bool LogicalOpParentheses = true; | 
|  | if (((*OuterBO == NewOp) || (!LogicalOpParentheses && | 
|  | (*OuterBO == BO_LOr && NewOp == BO_LAnd))) && | 
|  | Parens) { | 
|  | if (!Parens->getLParen().isMacroID() && | 
|  | !Parens->getRParen().isMacroID()) { | 
|  | Fixes.push_back(FixItHint::CreateRemoval(Parens->getLParen())); | 
|  | Fixes.push_back(FixItHint::CreateRemoval(Parens->getRParen())); | 
|  | } | 
|  | } | 
|  | if (*OuterBO == BO_LAnd && NewOp == BO_LOr && !Parens) { | 
|  | Fixes.push_back(FixItHint::CreateInsertion(BinOp->getBeginLoc(), "(")); | 
|  | Fixes.push_back(FixItHint::CreateInsertion( | 
|  | Lexer::getLocForEndOfToken(BinOp->getEndLoc(), 0, | 
|  | Ctx.getSourceManager(), | 
|  | Ctx.getLangOpts()), | 
|  | ")")); | 
|  | } | 
|  | } | 
|  | if (flipDemorganSide(Fixes, Ctx, BinOp->getLHS(), NewOp) || | 
|  | flipDemorganSide(Fixes, Ctx, BinOp->getRHS(), NewOp)) | 
|  | return true; | 
|  | return false; | 
|  | }; | 
|  | case BO_LT: | 
|  | case BO_GT: | 
|  | case BO_LE: | 
|  | case BO_GE: | 
|  | case BO_EQ: | 
|  | case BO_NE: | 
|  | // For comparison operators, just negate the comparison. | 
|  | if (BinOp->getOperatorLoc().isMacroID()) | 
|  | return true; | 
|  | Fixes.push_back(FixItHint::CreateReplacement( | 
|  | BinOp->getOperatorLoc(), | 
|  | BinaryOperator::getOpcodeStr( | 
|  | BinaryOperator::negateComparisonOp(BinOp->getOpcode())))); | 
|  | return false; | 
|  | default: | 
|  | // for any other binary operator, just use logical not and wrap in | 
|  | // parens. | 
|  | if (Parens) { | 
|  | if (Parens->getBeginLoc().isMacroID()) | 
|  | return true; | 
|  | Fixes.push_back(FixItHint::CreateInsertion(Parens->getBeginLoc(), "!")); | 
|  | } else { | 
|  | if (BinOp->getBeginLoc().isMacroID() || BinOp->getEndLoc().isMacroID()) | 
|  | return true; | 
|  | Fixes.append({FixItHint::CreateInsertion(BinOp->getBeginLoc(), "!("), | 
|  | FixItHint::CreateInsertion( | 
|  | Lexer::getLocForEndOfToken(BinOp->getEndLoc(), 0, | 
|  | Ctx.getSourceManager(), | 
|  | Ctx.getLangOpts()), | 
|  | ")")}); | 
|  | } | 
|  | break; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool flipDemorganSide(SmallVectorImpl<FixItHint> &Fixes, | 
|  | const ASTContext &Ctx, const Expr *E, | 
|  | std::optional<BinaryOperatorKind> OuterBO) { | 
|  | if (isa<UnaryOperator>(E) && cast<UnaryOperator>(E)->getOpcode() == UO_LNot) { | 
|  | //  if we have a not operator, '!a', just remove the '!'. | 
|  | if (cast<UnaryOperator>(E)->getOperatorLoc().isMacroID()) | 
|  | return true; | 
|  | Fixes.push_back( | 
|  | FixItHint::CreateRemoval(cast<UnaryOperator>(E)->getOperatorLoc())); | 
|  | return false; | 
|  | } | 
|  | if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) { | 
|  | return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO); | 
|  | } | 
|  | if (const auto *Paren = dyn_cast<ParenExpr>(E)) { | 
|  | if (const auto *BinOp = dyn_cast<BinaryOperator>(Paren->getSubExpr())) { | 
|  | return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO, Paren); | 
|  | } | 
|  | } | 
|  | // Fallback case just insert a logical not operator. | 
|  | if (E->getBeginLoc().isMacroID()) | 
|  | return true; | 
|  | Fixes.push_back(FixItHint::CreateInsertion(E->getBeginLoc(), "!")); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool shouldRemoveParens(const Stmt *Parent, | 
|  | BinaryOperatorKind NewOuterBinary, | 
|  | const ParenExpr *Parens) { | 
|  | if (!Parens) | 
|  | return false; | 
|  | if (!Parent) | 
|  | return true; | 
|  | switch (Parent->getStmtClass()) { | 
|  | case Stmt::BinaryOperatorClass: { | 
|  | const auto *BO = cast<BinaryOperator>(Parent); | 
|  | if (BO->isAssignmentOp()) | 
|  | return true; | 
|  | if (BO->isCommaOp()) | 
|  | return true; | 
|  | if (BO->getOpcode() == NewOuterBinary) | 
|  | return true; | 
|  | return false; | 
|  | } | 
|  | case Stmt::UnaryOperatorClass: | 
|  | case Stmt::CXXRewrittenBinaryOperatorClass: | 
|  | return false; | 
|  | default: | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool SimplifyBooleanExprCheck::reportDeMorgan(const ASTContext &Context, | 
|  | const UnaryOperator *Outer, | 
|  | const BinaryOperator *Inner, | 
|  | bool TryOfferFix, | 
|  | const Stmt *Parent, | 
|  | const ParenExpr *Parens) { | 
|  | assert(Outer); | 
|  | assert(Inner); | 
|  | assert(Inner->isLogicalOp()); | 
|  |  | 
|  | auto Diag = | 
|  | diag(Outer->getBeginLoc(), | 
|  | "boolean expression can be simplified by DeMorgan's theorem"); | 
|  | Diag << Outer->getSourceRange(); | 
|  | // If we have already fixed this with a previous fix, don't attempt any fixes | 
|  | if (!TryOfferFix) | 
|  | return false; | 
|  | if (Outer->getOperatorLoc().isMacroID()) | 
|  | return false; | 
|  | SmallVector<FixItHint> Fixes; | 
|  | auto NewOpcode = getDemorganFlippedOperator(Inner->getOpcode()); | 
|  | if (shouldRemoveParens(Parent, NewOpcode, Parens)) { | 
|  | Fixes.push_back(FixItHint::CreateRemoval( | 
|  | SourceRange(Outer->getOperatorLoc(), Parens->getLParen()))); | 
|  | Fixes.push_back(FixItHint::CreateRemoval(Parens->getRParen())); | 
|  | } else { | 
|  | Fixes.push_back(FixItHint::CreateRemoval(Outer->getOperatorLoc())); | 
|  | } | 
|  | if (flipDemorganOperator(Fixes, Inner)) | 
|  | return false; | 
|  | if (flipDemorganSide(Fixes, Context, Inner->getLHS(), NewOpcode) || | 
|  | flipDemorganSide(Fixes, Context, Inner->getRHS(), NewOpcode)) | 
|  | return false; | 
|  | Diag << Fixes; | 
|  | return true; | 
|  | } | 
|  | } // namespace clang::tidy::readability |