| use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; | |
| use clippy_utils::source::snippet_with_context; | |
| 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, 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_context(cx, cond, e.span.ctxt(), "..", &mut applicability); | |
| let (lhs, _) = snippet_with_context(cx, lhs_a.span, e.span.ctxt(), "..", &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(_, _))) | |
| } |