| 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) | |
| } |