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