| use clippy_config::Conf; |
| use clippy_utils::diagnostics::span_lint_and_then; |
| use clippy_utils::eager_or_lazy::switch_to_eager_eval; |
| use clippy_utils::msrvs::{self, Msrv}; |
| use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; |
| use clippy_utils::sugg::Sugg; |
| use clippy_utils::visitors::for_each_expr_without_closures; |
| use clippy_utils::{ |
| as_some_expr, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, is_none_expr, |
| peel_blocks, sym, |
| }; |
| use core::ops::ControlFlow; |
| use rustc_errors::Applicability; |
| use rustc_hir::{Expr, ExprKind}; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_session::impl_lint_pass; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Checks for if-else that could be written using either `bool::then` or `bool::then_some`. |
| /// |
| /// ### Why restrict this? |
| /// Looks a little redundant. Using `bool::then` is more concise and incurs no loss of clarity. |
| /// For simple calculations and known values, use `bool::then_some`, which is eagerly evaluated |
| /// in comparison to `bool::then`. |
| /// |
| /// ### Example |
| /// ```no_run |
| /// # let v = vec![0]; |
| /// let a = if v.is_empty() { |
| /// println!("true!"); |
| /// Some(42) |
| /// } else { |
| /// None |
| /// }; |
| /// ``` |
| /// |
| /// Could be written: |
| /// |
| /// ```no_run |
| /// # let v = vec![0]; |
| /// let a = v.is_empty().then(|| { |
| /// println!("true!"); |
| /// 42 |
| /// }); |
| /// ``` |
| #[clippy::version = "1.53.0"] |
| pub IF_THEN_SOME_ELSE_NONE, |
| restriction, |
| "Finds if-else that could be written using either `bool::then` or `bool::then_some`" |
| } |
| |
| impl_lint_pass!(IfThenSomeElseNone => [IF_THEN_SOME_ELSE_NONE]); |
| |
| pub struct IfThenSomeElseNone { |
| msrv: Msrv, |
| } |
| |
| impl IfThenSomeElseNone { |
| pub fn new(conf: &'static Conf) -> Self { |
| Self { msrv: conf.msrv } |
| } |
| } |
| |
| impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { |
| if let Some(higher::If { |
| cond, |
| then, |
| r#else: Some(els), |
| }) = higher::If::hir(expr) |
| && let ExprKind::Block(then_block, _) = then.kind |
| && let Some(then_expr) = then_block.expr |
| && let Some(then_arg) = as_some_expr(cx, then_expr) |
| && !expr.span.from_expansion() |
| && !then_expr.span.from_expansion() |
| && is_none_expr(cx, peel_blocks(els)) |
| && !is_else_clause(cx.tcx, expr) |
| && !is_in_const_context(cx) |
| && self.msrv.meets(cx, msrvs::BOOL_THEN) |
| && for_each_expr_without_closures(then_block, |e| { |
| if matches!(e.kind, ExprKind::Ret(..) | ExprKind::Yield(..)) { |
| ControlFlow::Break(()) |
| } else { |
| ControlFlow::Continue(()) |
| } |
| }) |
| .is_none() |
| { |
| let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) { |
| sym::then_some |
| } else { |
| sym::then |
| }; |
| let ctxt = expr.span.ctxt(); |
| |
| span_lint_and_then( |
| cx, |
| IF_THEN_SOME_ELSE_NONE, |
| expr.span, |
| format!("this could be simplified with `bool::{method_name}`"), |
| |diag| { |
| if expr_adjustment_requires_coercion(cx, then_arg) { |
| return; |
| } |
| |
| let mut app = Applicability::MachineApplicable; |
| let cond_snip = Sugg::hir_with_context(cx, cond, ctxt, "[condition]", &mut app) |
| .maybe_paren() |
| .to_string(); |
| let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; |
| let method_body = if let Some(_) = then_block.stmts.first() |
| && let Some(then_span) = walk_span_to_context(then.span, ctxt) |
| { |
| let block_before_snippet = |
| snippet_with_applicability(cx, then_span.until(then_expr.span), "..", &mut app); |
| let block_after_snippet = snippet_with_applicability( |
| cx, |
| then_expr.span.shrink_to_hi().until(then_span.shrink_to_hi()), |
| "..", |
| &mut app, |
| ); |
| let closure = if method_name == sym::then { "|| " } else { "" }; |
| format!("{closure}{block_before_snippet}{arg_snip}{block_after_snippet}") |
| } else if method_name == sym::then { |
| (std::borrow::Cow::Borrowed("|| ") + arg_snip).into_owned() |
| } else { |
| arg_snip.into_owned() |
| }; |
| |
| diag.span_suggestion( |
| expr.span, |
| "try", |
| format!("{cond_snip}.{method_name}({method_body})"), |
| app, |
| ); |
| }, |
| ); |
| } |
| } |
| } |