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