]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/manual_unwrap_or.rs
Auto merge of #7233 - giraffate:fix_manual_unwrap_or_fp_with_deref_coercion, r=flip1995
[rust.git] / clippy_lints / src / manual_unwrap_or.rs
1 use crate::consts::constant_simple;
2 use clippy_utils::diagnostics::span_lint_and_sugg;
3 use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
4 use clippy_utils::ty::is_type_diagnostic_item;
5 use clippy_utils::usage::contains_return_break_continue_macro;
6 use clippy_utils::{in_constant, is_lang_ctor, path_to_local_id, sugg};
7 use if_chain::if_chain;
8 use rustc_errors::Applicability;
9 use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
10 use rustc_hir::{Arm, Expr, ExprKind, PatKind};
11 use rustc_lint::LintContext;
12 use rustc_lint::{LateContext, LateLintPass};
13 use rustc_middle::lint::in_external_macro;
14 use rustc_middle::ty::adjustment::Adjust;
15 use rustc_session::{declare_lint_pass, declare_tool_lint};
16 use rustc_span::sym;
17
18 declare_clippy_lint! {
19     /// **What it does:**
20     /// Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`.
21     ///
22     /// **Why is this bad?**
23     /// Concise code helps focusing on behavior instead of boilerplate.
24     ///
25     /// **Known problems:** None.
26     ///
27     /// **Example:**
28     /// ```rust
29     /// let foo: Option<i32> = None;
30     /// match foo {
31     ///     Some(v) => v,
32     ///     None => 1,
33     /// };
34     /// ```
35     ///
36     /// Use instead:
37     /// ```rust
38     /// let foo: Option<i32> = None;
39     /// foo.unwrap_or(1);
40     /// ```
41     pub MANUAL_UNWRAP_OR,
42     complexity,
43     "finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`"
44 }
45
46 declare_lint_pass!(ManualUnwrapOr => [MANUAL_UNWRAP_OR]);
47
48 impl LateLintPass<'_> for ManualUnwrapOr {
49     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
50         if in_external_macro(cx.sess(), expr.span) || in_constant(cx, expr.hir_id) {
51             return;
52         }
53         lint_manual_unwrap_or(cx, expr);
54     }
55 }
56
57 #[derive(Copy, Clone)]
58 enum Case {
59     Option,
60     Result,
61 }
62
63 impl Case {
64     fn unwrap_fn_path(&self) -> &str {
65         match self {
66             Case::Option => "Option::unwrap_or",
67             Case::Result => "Result::unwrap_or",
68         }
69     }
70 }
71
72 fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
73     fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
74         if_chain! {
75             if arms.len() == 2;
76             if arms.iter().all(|arm| arm.guard.is_none());
77             if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| {
78                 match arm.pat.kind {
79                     PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
80                     PatKind::TupleStruct(ref qpath, &[pat], _) =>
81                         matches!(pat.kind, PatKind::Wild) && is_lang_ctor(cx, qpath, ResultErr),
82                     _ => false,
83                 }
84             });
85             let unwrap_arm = &arms[1 - idx];
86             if let PatKind::TupleStruct(ref qpath, &[unwrap_pat], _) = unwrap_arm.pat.kind;
87             if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
88             if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind;
89             if path_to_local_id(unwrap_arm.body, binding_hir_id);
90             if !contains_return_break_continue_macro(or_arm.body);
91             if !cx.typeck_results().expr_adjustments(unwrap_arm.body).iter()
92                 .any(|a| matches!(a.kind, Adjust::Deref(Some(..))));
93             then {
94                 Some(or_arm)
95             } else {
96                 None
97             }
98         }
99     }
100
101     if_chain! {
102         if let ExprKind::Match(scrutinee, match_arms, _) = expr.kind;
103         let ty = cx.typeck_results().expr_ty(scrutinee);
104         if let Some(case) = if is_type_diagnostic_item(cx, ty, sym::option_type) {
105             Some(Case::Option)
106         } else if is_type_diagnostic_item(cx, ty, sym::result_type) {
107             Some(Case::Result)
108         } else {
109             None
110         };
111         if let Some(or_arm) = applicable_or_arm(cx, match_arms);
112         if let Some(or_body_snippet) = snippet_opt(cx, or_arm.body.span);
113         if let Some(indent) = indent_of(cx, expr.span);
114         if constant_simple(cx, cx.typeck_results(), or_arm.body).is_some();
115         then {
116             let reindented_or_body =
117                 reindent_multiline(or_body_snippet.into(), true, Some(indent));
118
119             let suggestion = if scrutinee.span.from_expansion() {
120                     // we don't want parenthesis around macro, e.g. `(some_macro!()).unwrap_or(0)`
121                     sugg::Sugg::hir_with_macro_callsite(cx, scrutinee, "..")
122                 }
123                 else {
124                     sugg::Sugg::hir(cx, scrutinee, "..").maybe_par()
125                 };
126
127             span_lint_and_sugg(
128                 cx,
129                 MANUAL_UNWRAP_OR, expr.span,
130                 &format!("this pattern reimplements `{}`", case.unwrap_fn_path()),
131                 "replace with",
132                 format!(
133                     "{}.unwrap_or({})",
134                     suggestion,
135                     reindented_or_body,
136                 ),
137                 Applicability::MachineApplicable,
138             );
139         }
140     }
141 }