[clang-tidy] Enhance clang-tidy readability-simplify-boolean-expr...
Enhance clang-tidy readability-simplify-boolean-expr to handle 'if (e) return
true; return false;' and improve replacement expressions.
This changeset extends the simplify boolean expression check in clang-tidy to
simplify if (e) return true; return false; to return e; (note the lack of an
else clause on the if statement.) By default, chained conditional assignment is
left unchanged, unless a configuration parameter is set to non-zero to override
this behavior.
It also improves the handling of replacement expressions to apply
static_cast<bool>(expr) when expr is not of type bool.
http://reviews.llvm.org/D9810
Patch by Richard Thomson!
llvm-svn: 241155
diff --git a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
index f7754e4..704a49e 100644
--- a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
@@ -12,6 +12,7 @@
#include <cassert>
#include <string>
+#include <utility>
using namespace clang::ast_matchers;
@@ -48,6 +49,9 @@
const char IfAssignBoolId[] = "if-assign";
const char IfAssignNotBoolId[] = "if-assign-not";
const char IfAssignObjId[] = "if-assign-obj";
+const char CompoundReturnId[] = "compound-return";
+const char CompoundBoolId[] = "compound-bool";
+const char CompoundNotBoolId[] = "compound-bool-not";
const char IfStmtId[] = "if";
const char LHSId[] = "lhs-expr";
@@ -57,6 +61,8 @@
"redundant boolean literal supplied to boolean operator";
const char SimplifyConditionDiagnostic[] =
"redundant boolean literal in if statement condition";
+const char SimplifyConditionalReturnDiagnostic[] =
+ "redundant boolean literal in conditional return statement";
const CXXBoolLiteralExpr *getBoolLiteral(const MatchFinder::MatchResult &Result,
StringRef Id) {
@@ -67,25 +73,26 @@
: Literal;
}
-internal::Matcher<Stmt> ReturnsBool(bool Value, StringRef Id = "") {
- auto SimpleReturnsBool = returnStmt(
- has(boolLiteral(equals(Value)).bind(Id.empty() ? "ignored" : Id)));
+internal::Matcher<Stmt> returnsBool(bool Value, StringRef Id = "ignored") {
+ auto SimpleReturnsBool =
+ returnStmt(has(boolLiteral(equals(Value)).bind(Id))).bind("returns-bool");
return anyOf(SimpleReturnsBool,
compoundStmt(statementCountIs(1), has(SimpleReturnsBool)));
}
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))
+ if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E)) {
return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call &&
Op->getOperator() != OO_Subscript;
+ }
return false;
}
std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = {
- std::make_pair(BO_LT, BO_GE), std::make_pair(BO_GT, BO_LE),
- std::make_pair(BO_EQ, BO_NE)};
+ {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}};
StringRef negatedOperator(const BinaryOperator *BinOp) {
const BinaryOperatorKind Opcode = BinOp->getOpcode();
@@ -98,24 +105,153 @@
return StringRef();
}
-std::string replacementExpression(const MatchFinder::MatchResult &Result,
- bool Negated, const Expr *E) {
- while (const auto *Parenthesized = dyn_cast<ParenExpr>(E)) {
- E = Parenthesized->getSubExpr();
+std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = {
+ {OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"},
+ {OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}};
+
+StringRef getOperatorName(OverloadedOperatorKind OpKind) {
+ for (auto Name : OperatorNames) {
+ if (Name.first == OpKind)
+ return Name.second;
}
- if (Negated) {
- if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
- StringRef NegatedOperator = negatedOperator(BinOp);
- if (!NegatedOperator.empty()) {
- return (getText(Result, *BinOp->getLHS()) + " " + NegatedOperator +
- " " + getText(Result, *BinOp->getRHS())).str();
+
+ return StringRef();
+}
+
+std::pair<OverloadedOperatorKind, OverloadedOperatorKind> OppositeOverloads[] =
+ {{OO_EqualEqual, OO_ExclaimEqual},
+ {OO_Less, OO_GreaterEqual},
+ {OO_Greater, OO_LessEqual}};
+
+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 StringRef();
+}
+
+std::string asBool(StringRef text, bool NeedsStaticCast) {
+ if (NeedsStaticCast)
+ return ("static_cast<bool>(" + text + ")").str();
+
+ return text;
+}
+
+bool needsNullPtrComparison(const Expr *E) {
+ if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
+ return ImpCast->getCastKind() == CK_PointerToBoolean;
+
+ return false;
+}
+
+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;
+ }
}
}
}
- StringRef Text = getText(Result, *E);
- return (Negated ? (needsParensAfterUnaryNegation(E) ? "!(" + Text + ")"
- : "!" + Text)
- : Text).str();
+
+ E = E->IgnoreImpCasts();
+ return !E->getType()->isBooleanType();
+}
+
+std::string replacementExpression(const MatchFinder::MatchResult &Result,
+ bool Negated, const Expr *E) {
+ E = E->ignoreParenBaseCasts();
+ 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 (getText(Result, *UnOp->getSubExpr()) + " != nullptr").str();
+
+ return replacementExpression(Result, false, UnOp->getSubExpr());
+ }
+ }
+
+ if (needsNullPtrComparison(E))
+ return (getText(Result, *E) + " == nullptr").str();
+
+ 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(Result, *LHS) + " " + NegatedOperator + " " +
+ getText(Result, *RHS))
+ .str(),
+ NeedsStaticCast));
+ }
+
+ StringRef Text = getText(Result, *E);
+ if (!NeedsStaticCast && needsParensAfterUnaryNegation(E))
+ return ("!(" + Text + ")").str();
+
+ if (needsNullPtrComparison(E))
+ return (getText(Result, *E) + " == nullptr").str();
+
+ return ("!" + asBool(Text, NeedsStaticCast));
+ }
+
+ if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
+ if (UnOp->getOpcode() == UO_LNot) {
+ if (needsNullPtrComparison(UnOp->getSubExpr()))
+ return (getText(Result, *UnOp->getSubExpr()) + " == nullptr").str();
+ }
+ }
+
+ if (needsNullPtrComparison(E))
+ return (getText(Result, *E) + " != nullptr").str();
+
+ return asBool(getText(Result, *E), NeedsStaticCast);
+}
+
+const CXXBoolLiteralExpr *stmtReturnsBool(const ReturnStmt *Ret, bool Negated) {
+ if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(Ret->getRetValue())) {
+ if (Bool->getValue() == !Negated)
+ return Bool;
+ }
+
+ return nullptr;
+}
+
+const CXXBoolLiteralExpr *stmtReturnsBool(const IfStmt *IfRet, bool Negated) {
+ if (IfRet->getElse() != nullptr)
+ return nullptr;
+
+ if (const auto *Ret = dyn_cast<ReturnStmt>(IfRet->getThen()))
+ return stmtReturnsBool(Ret, Negated);
+
+ if (const auto *Compound = dyn_cast<CompoundStmt>(IfRet->getThen())) {
+ if (Compound->size() == 1) {
+ if (const auto *CompoundRet = dyn_cast<ReturnStmt>(Compound->body_back()))
+ return stmtReturnsBool(CompoundRet, Negated);
+ }
+ }
+
+ return nullptr;
}
} // namespace
@@ -185,10 +321,11 @@
void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder,
bool Value,
StringRef BooleanId) {
- Finder->addMatcher(ifStmt(isExpansionInMainFile(),
- hasCondition(boolLiteral(equals(Value))
- .bind(BooleanId))).bind(IfStmtId),
- this);
+ Finder->addMatcher(
+ ifStmt(isExpansionInMainFile(),
+ hasCondition(boolLiteral(equals(Value)).bind(BooleanId)))
+ .bind(IfStmtId),
+ this);
}
void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder,
@@ -206,14 +343,16 @@
bool Value, StringRef Id) {
if (ChainedConditionalReturn) {
Finder->addMatcher(ifStmt(isExpansionInMainFile(),
- hasThen(ReturnsBool(Value, ThenLiteralId)),
- hasElse(ReturnsBool(!Value))).bind(Id),
+ hasThen(returnsBool(Value, ThenLiteralId)),
+ hasElse(returnsBool(!Value)))
+ .bind(Id),
this);
} else {
Finder->addMatcher(ifStmt(isExpansionInMainFile(),
unless(hasParent(ifStmt())),
- hasThen(ReturnsBool(Value, ThenLiteralId)),
- hasElse(ReturnsBool(!Value))).bind(Id),
+ hasThen(returnsBool(Value, ThenLiteralId)),
+ hasElse(returnsBool(!Value)))
+ .bind(Id),
this);
}
}
@@ -240,11 +379,25 @@
} else {
Finder->addMatcher(ifStmt(isExpansionInMainFile(),
unless(hasParent(ifStmt())), hasThen(Then),
- hasElse(Else)).bind(Id),
+ hasElse(Else))
+ .bind(Id),
this);
}
}
+void SimplifyBooleanExprCheck::matchCompoundIfReturnsBool(MatchFinder *Finder,
+ bool Value,
+ StringRef Id) {
+ Finder->addMatcher(
+ compoundStmt(
+ allOf(hasAnySubstatement(ifStmt(hasThen(returnsBool(Value)),
+ unless(hasElse(stmt())))),
+ hasAnySubstatement(returnStmt(has(boolLiteral(equals(!Value))))
+ .bind(CompoundReturnId))))
+ .bind(Id),
+ this);
+}
+
void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn);
Options.store(Opts, "ChainedConditionalAssignment",
@@ -283,6 +436,9 @@
matchIfAssignsBool(Finder, true, IfAssignBoolId);
matchIfAssignsBool(Finder, false, IfAssignNotBoolId);
+
+ matchCompoundIfReturnsBool(Finder, true, CompoundBoolId);
+ matchCompoundIfReturnsBool(Finder, false, CompoundNotBoolId);
}
void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
@@ -322,6 +478,12 @@
} else if (const auto *IfAssignNot =
Result.Nodes.getNodeAs<IfStmt>(IfAssignNotBoolId)) {
replaceWithAssignment(Result, IfAssignNot, true);
+ } else if (const auto *Compound =
+ Result.Nodes.getNodeAs<CompoundStmt>(CompoundBoolId)) {
+ replaceCompoundReturnWithCondition(Result, Compound);
+ } else if (const auto *Compound =
+ Result.Nodes.getNodeAs<CompoundStmt>(CompoundNotBoolId)) {
+ replaceCompoundReturnWithCondition(Result, Compound, true);
}
}
@@ -375,10 +537,51 @@
std::string Replacement = ("return " + Condition + Terminator).str();
SourceLocation Start =
Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getLocStart();
- diag(Start, "redundant boolean literal in conditional return statement")
+ diag(Start, SimplifyConditionalReturnDiagnostic)
<< FixItHint::CreateReplacement(If->getSourceRange(), Replacement);
}
+void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
+ const MatchFinder::MatchResult &Result, const CompoundStmt *Compound,
+ bool Negated) {
+ const auto *Ret = Result.Nodes.getNodeAs<ReturnStmt>(CompoundReturnId);
+
+ // The body shouldn't be empty because the matcher ensures that it must
+ // contain at least two statements:
+ // 1) A `return` statement returning a boolean literal `false` or `true`
+ // 2) An `if` statement with no `else` clause that consists fo a single
+ // `return` statement returning the opposite boolean literal `true` or
+ // `false`.
+ assert(Compound->size() >= 2);
+ const IfStmt *BeforeIf = nullptr;
+ CompoundStmt::const_body_iterator Current = Compound->body_begin();
+ CompoundStmt::const_body_iterator After = Compound->body_begin();
+ for (++After; After != Compound->body_end() && *Current != Ret;
+ ++Current, ++After) {
+ if (const auto *If = dyn_cast<IfStmt>(*Current)) {
+ if (const CXXBoolLiteralExpr *Lit = stmtReturnsBool(If, Negated)) {
+ if (*After == Ret) {
+ if (!ChainedConditionalReturn && BeforeIf)
+ continue;
+
+ const Expr *Condition = If->getCond();
+ std::string Replacement =
+ "return " + replacementExpression(Result, Negated, Condition);
+ diag(Lit->getLocStart(), SimplifyConditionalReturnDiagnostic)
+ << FixItHint::CreateReplacement(
+ SourceRange(If->getLocStart(), Ret->getLocEnd()),
+ Replacement);
+ return;
+ }
+
+ BeforeIf = If;
+ }
+ } else {
+ BeforeIf = nullptr;
+ }
+ }
+}
+
void SimplifyBooleanExprCheck::replaceWithAssignment(
const MatchFinder::MatchResult &Result, const IfStmt *IfAssign,
bool Negated) {