blob: ff22ba4fcd0d607e5bf5fd4739e491d1aece8baa [file]
use clippy_utils::consts::is_zero_integer_const;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::is_else_clause;
use clippy_utils::source::{HasSession, indent_of, reindent_multiline, snippet_with_context};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `!` or `!=` in an if condition with an
/// else branch.
///
/// ### Why is this bad?
/// Negations reduce the readability of statements.
///
/// ### Example
/// ```no_run
/// # let v: Vec<usize> = vec![];
/// # fn a() {}
/// # fn b() {}
/// if !v.is_empty() {
/// a()
/// } else {
/// b()
/// }
/// ```
///
/// Could be written:
///
/// ```no_run
/// # let v: Vec<usize> = vec![];
/// # fn a() {}
/// # fn b() {}
/// if v.is_empty() {
/// b()
/// } else {
/// a()
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub IF_NOT_ELSE,
pedantic,
"`if` branches that could be swapped so no negation operation is necessary on the condition"
}
declare_lint_pass!(IfNotElse => [IF_NOT_ELSE]);
impl LateLintPass<'_> for IfNotElse {
fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
if let ExprKind::If(cond, cond_inner, Some(els)) = e.kind
&& let ExprKind::Block(..) = els.kind
{
let (msg, help) = match cond.kind {
ExprKind::Unary(UnOp::Not, _) => (
"unnecessary boolean `not` operation",
"remove the `!` and swap the blocks of the `if`/`else`",
),
// Don't lint on `… != 0`, as these are likely to be bit tests.
// For example, `if foo & 0x0F00 != 0 { … } else { … }` is already in the "proper" order.
ExprKind::Binary(op, _, rhs)
if op.node == BinOpKind::Ne && !is_zero_integer_const(cx, rhs, e.span.ctxt()) =>
{
(
"unnecessary `!=` operation",
"change to `==` and swap the blocks of the `if`/`else`",
)
},
_ => return,
};
// `from_expansion` will also catch `while` loops which appear in the HIR as:
// ```rust
// loop {
// if cond { ... } else { break; }
// }
// ```
if !e.span.from_expansion() && !is_else_clause(cx.tcx, e) {
let mut applicability = Applicability::MachineApplicable;
match cond.kind {
ExprKind::Unary(UnOp::Not, _) | ExprKind::Binary(_, _, _) => span_lint_and_sugg(
cx,
IF_NOT_ELSE,
e.span,
msg,
"try",
make_sugg(
cx,
e.span,
&cond.kind,
cond_inner.span,
els.span,
"..",
&mut applicability,
),
applicability,
),
_ => span_lint_and_help(cx, IF_NOT_ELSE, e.span, msg, None, help),
}
}
}
}
}
fn make_sugg<'a>(
sess: &impl HasSession,
expr_span: Span,
cond_kind: &'a ExprKind<'a>,
cond_inner: Span,
els_span: Span,
default: &'a str,
applicability: &mut Applicability,
) -> String {
let (cond_inner_snip, _) = snippet_with_context(sess, cond_inner, expr_span.ctxt(), default, applicability);
let (els_snip, _) = snippet_with_context(sess, els_span, expr_span.ctxt(), default, applicability);
let indent = indent_of(sess, expr_span);
let suggestion = match cond_kind {
ExprKind::Unary(UnOp::Not, cond_rest) => {
let (cond_rest_snip, _) =
snippet_with_context(sess, cond_rest.span, expr_span.ctxt(), default, applicability);
format!("if {cond_rest_snip} {els_snip} else {cond_inner_snip}")
},
ExprKind::Binary(_, lhs, rhs) => {
let (lhs_snip, _) = snippet_with_context(sess, lhs.span, expr_span.ctxt(), default, applicability);
let (rhs_snip, _) = snippet_with_context(sess, rhs.span, expr_span.ctxt(), default, applicability);
format!("if {lhs_snip} == {rhs_snip} {els_snip} else {cond_inner_snip}")
},
_ => String::new(),
};
reindent_multiline(&suggestion, true, indent)
}