blob: 854e927aa2f781c658432c3b6a419b058def1e5e [file] [log] [blame] [edit]
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{
SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_parent_stmt, is_receiver_of_method_call,
peel_blocks, peel_blocks_with_stmt, span_contains_comment,
};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for expressions of the form `if c { true } else {
/// false }` (or vice versa) and suggests using the condition directly.
///
/// ### Why is this bad?
/// Redundant code.
///
/// ### Known problems
/// Maybe false positives: Sometimes, the two branches are
/// painstakingly documented (which we, of course, do not detect), so they *may*
/// have some value. Even then, the documentation can be rewritten to match the
/// shorter code.
///
/// ### Example
/// ```no_run
/// # let x = true;
/// if x {
/// false
/// } else {
/// true
/// }
/// # ;
/// ```
///
/// Use instead:
/// ```no_run
/// # let x = true;
/// !x
/// # ;
/// ```
#[clippy::version = "pre 1.29.0"]
pub NEEDLESS_BOOL,
complexity,
"if-statements with plain booleans in the then- and else-clause, e.g., `if p { true } else { false }`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for expressions of the form `if c { x = true } else { x = false }`
/// (or vice versa) and suggest assigning the variable directly from the
/// condition.
///
/// ### Why is this bad?
/// Redundant code.
///
/// ### Example
/// ```rust,ignore
/// # fn must_keep(x: i32, y: i32) -> bool { x == y }
/// # let x = 32; let y = 10;
/// # let mut skip: bool;
/// if must_keep(x, y) {
/// skip = false;
/// } else {
/// skip = true;
/// }
/// ```
/// Use instead:
/// ```rust,ignore
/// # fn must_keep(x: i32, y: i32) -> bool { x == y }
/// # let x = 32; let y = 10;
/// # let mut skip: bool;
/// skip = !must_keep(x, y);
/// ```
#[clippy::version = "1.71.0"]
pub NEEDLESS_BOOL_ASSIGN,
complexity,
"setting the same boolean variable in both branches of an if-statement"
}
declare_lint_pass!(NeedlessBool => [NEEDLESS_BOOL, NEEDLESS_BOOL_ASSIGN]);
fn condition_needs_parentheses(e: &Expr<'_>) -> bool {
let mut inner = e;
while let ExprKind::Binary(_, i, _)
| ExprKind::Call(i, _)
| ExprKind::Cast(i, _)
| ExprKind::Type(i, _)
| ExprKind::Index(i, _, _) = inner.kind
{
if is_block_like(i) {
return true;
}
inner = i;
}
false
}
impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
use self::Expression::{Bool, RetBool};
if !e.span.from_expansion()
&& let Some(higher::If {
cond,
then,
r#else: Some(else_expr),
}) = higher::If::hir(e)
&& !span_contains_comment(cx.tcx.sess.source_map(), e.span)
{
let reduce = |ret, not| {
let mut applicability = Applicability::MachineApplicable;
let snip = Sugg::hir_with_applicability(cx, cond, "<predicate>", &mut applicability);
let mut snip = if not { !snip } else { snip };
if ret {
snip = snip.make_return();
}
if is_else_clause(cx.tcx, e) {
snip = snip.blockify();
}
if (condition_needs_parentheses(cond) && is_parent_stmt(cx, e.hir_id))
|| is_receiver_of_method_call(cx, e)
|| is_as_argument(cx, e)
{
snip = snip.maybe_paren();
}
span_lint_and_sugg(
cx,
NEEDLESS_BOOL,
e.span,
"this if-then-else expression returns a bool literal",
"you can reduce it to",
snip.to_string(),
applicability,
);
};
if let Some(a) = fetch_bool_block(then)
&& let Some(b) = fetch_bool_block(else_expr)
{
match (a, b) {
(RetBool(true), RetBool(true)) | (Bool(true), Bool(true)) => {
span_lint(
cx,
NEEDLESS_BOOL,
e.span,
"this if-then-else expression will always return true",
);
},
(RetBool(false), RetBool(false)) | (Bool(false), Bool(false)) => {
span_lint(
cx,
NEEDLESS_BOOL,
e.span,
"this if-then-else expression will always return false",
);
},
(RetBool(true), RetBool(false)) => reduce(true, false),
(Bool(true), Bool(false)) => reduce(false, false),
(RetBool(false), RetBool(true)) => reduce(true, true),
(Bool(false), Bool(true)) => reduce(false, true),
_ => (),
}
}
if let Some((lhs_a, a)) = fetch_assign(then)
&& let Some((lhs_b, b)) = fetch_assign(else_expr)
&& SpanlessEq::new(cx).eq_expr(lhs_a, lhs_b)
{
let mut applicability = Applicability::MachineApplicable;
let cond = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability);
let lhs = snippet_with_applicability(cx, lhs_a.span, "..", &mut applicability);
let mut sugg = if a == b {
format!("{cond}; {lhs} = {a:?};")
} else {
format!("{lhs} = {};", if a { cond } else { !cond })
};
if is_else_clause(cx.tcx, e) {
sugg = format!("{{ {sugg} }}");
}
span_lint_and_sugg(
cx,
NEEDLESS_BOOL_ASSIGN,
e.span,
"this if-then-else expression assigns a bool literal",
"you can reduce it to",
sugg,
applicability,
);
}
}
}
}
enum Expression {
Bool(bool),
RetBool(bool),
}
fn fetch_bool_block(expr: &Expr<'_>) -> Option<Expression> {
match peel_blocks_with_stmt(expr).kind {
ExprKind::Ret(Some(ret)) => Some(Expression::RetBool(fetch_bool_expr(ret)?)),
_ => Some(Expression::Bool(fetch_bool_expr(expr)?)),
}
}
fn fetch_bool_expr(expr: &Expr<'_>) -> Option<bool> {
if let ExprKind::Lit(lit_ptr) = peel_blocks(expr).kind
&& let LitKind::Bool(value) = lit_ptr.node
{
return Some(value);
}
None
}
fn fetch_assign<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, bool)> {
if let ExprKind::Assign(lhs, rhs, _) = peel_blocks_with_stmt(expr).kind {
fetch_bool_expr(rhs).map(|b| (lhs, b))
} else {
None
}
}
fn is_as_argument(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
matches!(get_parent_expr(cx, e).map(|e| e.kind), Some(ExprKind::Cast(_, _)))
}