]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::span_lint_and_help; |
f2b60f7d | 2 | use clippy_utils::eager_or_lazy::switch_to_eager_eval; |
487cf647 | 3 | use clippy_utils::msrvs::{self, Msrv}; |
353b0b11 FG |
4 | use clippy_utils::source::snippet_with_context; |
5 | use clippy_utils::sugg::Sugg; | |
487cf647 | 6 | use clippy_utils::{contains_return, higher, is_else_clause, is_res_lang_ctor, path_res, peel_blocks}; |
353b0b11 | 7 | use rustc_errors::Applicability; |
cdc7bbd5 | 8 | use rustc_hir::LangItem::{OptionNone, OptionSome}; |
a2a8927a | 9 | use rustc_hir::{Expr, ExprKind, Stmt, StmtKind}; |
cdc7bbd5 XL |
10 | use rustc_lint::{LateContext, LateLintPass, LintContext}; |
11 | use rustc_middle::lint::in_external_macro; | |
cdc7bbd5 XL |
12 | use rustc_session::{declare_tool_lint, impl_lint_pass}; |
13 | ||
14 | declare_clippy_lint! { | |
94222f64 | 15 | /// ### What it does |
f2b60f7d | 16 | /// Checks for if-else that could be written using either `bool::then` or `bool::then_some`. |
cdc7bbd5 | 17 | /// |
94222f64 | 18 | /// ### Why is this bad? |
f2b60f7d FG |
19 | /// Looks a little redundant. Using `bool::then` is more concise and incurs no loss of clarity. |
20 | /// For simple calculations and known values, use `bool::then_some`, which is eagerly evaluated | |
21 | /// in comparison to `bool::then`. | |
cdc7bbd5 | 22 | /// |
94222f64 | 23 | /// ### Example |
cdc7bbd5 XL |
24 | /// ```rust |
25 | /// # let v = vec![0]; | |
26 | /// let a = if v.is_empty() { | |
27 | /// println!("true!"); | |
28 | /// Some(42) | |
29 | /// } else { | |
30 | /// None | |
31 | /// }; | |
32 | /// ``` | |
33 | /// | |
34 | /// Could be written: | |
35 | /// | |
36 | /// ```rust | |
37 | /// # let v = vec![0]; | |
38 | /// let a = v.is_empty().then(|| { | |
39 | /// println!("true!"); | |
40 | /// 42 | |
41 | /// }); | |
42 | /// ``` | |
a2a8927a | 43 | #[clippy::version = "1.53.0"] |
cdc7bbd5 XL |
44 | pub IF_THEN_SOME_ELSE_NONE, |
45 | restriction, | |
f2b60f7d | 46 | "Finds if-else that could be written using either `bool::then` or `bool::then_some`" |
cdc7bbd5 XL |
47 | } |
48 | ||
49 | pub struct IfThenSomeElseNone { | |
487cf647 | 50 | msrv: Msrv, |
cdc7bbd5 XL |
51 | } |
52 | ||
53 | impl IfThenSomeElseNone { | |
54 | #[must_use] | |
487cf647 | 55 | pub fn new(msrv: Msrv) -> Self { |
cdc7bbd5 XL |
56 | Self { msrv } |
57 | } | |
58 | } | |
59 | ||
60 | impl_lint_pass!(IfThenSomeElseNone => [IF_THEN_SOME_ELSE_NONE]); | |
61 | ||
5099ac24 | 62 | impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { |
f2b60f7d | 63 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { |
487cf647 | 64 | if !self.msrv.meets(msrvs::BOOL_THEN) { |
cdc7bbd5 XL |
65 | return; |
66 | } | |
67 | ||
68 | if in_external_macro(cx.sess(), expr.span) { | |
69 | return; | |
70 | } | |
71 | ||
72 | // We only care about the top-most `if` in the chain | |
73 | if is_else_clause(cx.tcx, expr) { | |
74 | return; | |
75 | } | |
76 | ||
353b0b11 FG |
77 | let ctxt = expr.span.ctxt(); |
78 | ||
f2b60f7d FG |
79 | if let Some(higher::If { cond, then, r#else: Some(els) }) = higher::If::hir(expr) |
80 | && let ExprKind::Block(then_block, _) = then.kind | |
81 | && let Some(then_expr) = then_block.expr | |
82 | && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind | |
353b0b11 | 83 | && then_expr.span.ctxt() == ctxt |
2b03887a FG |
84 | && is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome) |
85 | && is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone) | |
f2b60f7d FG |
86 | && !stmts_contains_early_return(then_block.stmts) |
87 | { | |
353b0b11 FG |
88 | let mut app = Applicability::Unspecified; |
89 | let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app).maybe_par().to_string(); | |
90 | let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; | |
f2b60f7d FG |
91 | let mut method_body = if then_block.stmts.is_empty() { |
92 | arg_snip.into_owned() | |
93 | } else { | |
2b03887a | 94 | format!("{{ /* snippet */ {arg_snip} }}") |
f2b60f7d | 95 | }; |
487cf647 | 96 | let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(msrvs::BOOL_THEN_SOME) { |
f2b60f7d FG |
97 | "then_some" |
98 | } else { | |
99 | method_body.insert_str(0, "|| "); | |
100 | "then" | |
101 | }; | |
102 | ||
103 | let help = format!( | |
2b03887a | 104 | "consider using `bool::{method_name}` like: `{cond_snip}.{method_name}({method_body})`", |
f2b60f7d FG |
105 | ); |
106 | span_lint_and_help( | |
107 | cx, | |
108 | IF_THEN_SOME_ELSE_NONE, | |
109 | expr.span, | |
2b03887a | 110 | &format!("this could be simplified with `bool::{method_name}`"), |
f2b60f7d FG |
111 | None, |
112 | &help, | |
113 | ); | |
cdc7bbd5 XL |
114 | } |
115 | } | |
116 | ||
117 | extract_msrv_attr!(LateContext); | |
118 | } | |
a2a8927a XL |
119 | |
120 | fn stmts_contains_early_return(stmts: &[Stmt<'_>]) -> bool { | |
121 | stmts.iter().any(|stmt| { | |
122 | let Stmt { kind: StmtKind::Semi(e), .. } = stmt else { return false }; | |
123 | ||
124 | contains_return(e) | |
125 | }) | |
126 | } |