]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/option_map_or_none.rs
Split out `infalliable_detructuring_match`
[rust.git] / clippy_lints / src / methods / option_map_or_none.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::source::snippet;
3 use clippy_utils::ty::is_type_diagnostic_item;
4 use clippy_utils::{is_lang_ctor, path_def_id};
5 use rustc_errors::Applicability;
6 use rustc_hir as hir;
7 use rustc_hir::LangItem::{OptionNone, OptionSome};
8 use rustc_lint::LateContext;
9 use rustc_middle::ty::DefIdTree;
10 use rustc_span::symbol::sym;
11
12 use super::OPTION_MAP_OR_NONE;
13 use super::RESULT_MAP_OR_INTO_OPTION;
14
15 // The expression inside a closure may or may not have surrounding braces
16 // which causes problems when generating a suggestion.
17 fn reduce_unit_expression<'a>(
18     cx: &LateContext<'_>,
19     expr: &'a hir::Expr<'_>,
20 ) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
21     match expr.kind {
22         hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)),
23         hir::ExprKind::Block(block, _) => {
24             match (block.stmts, block.expr) {
25                 (&[], Some(inner_expr)) => {
26                     // If block only contains an expression,
27                     // reduce `|x| { x + 1 }` to `|x| x + 1`
28                     reduce_unit_expression(cx, inner_expr)
29                 },
30                 _ => None,
31             }
32         },
33         _ => None,
34     }
35 }
36
37 /// lint use of `_.map_or(None, _)` for `Option`s and `Result`s
38 pub(super) fn check<'tcx>(
39     cx: &LateContext<'tcx>,
40     expr: &'tcx hir::Expr<'_>,
41     recv: &'tcx hir::Expr<'_>,
42     def_arg: &'tcx hir::Expr<'_>,
43     map_arg: &'tcx hir::Expr<'_>,
44 ) {
45     let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
46     let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
47
48     // There are two variants of this `map_or` lint:
49     // (1) using `map_or` as an adapter from `Result<T,E>` to `Option<T>`
50     // (2) using `map_or` as a combinator instead of `and_then`
51     //
52     // (For this lint) we don't care if any other type calls `map_or`
53     if !is_option && !is_result {
54         return;
55     }
56
57     let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind {
58         is_lang_ctor(cx, qpath, OptionNone)
59     } else {
60         return;
61     };
62
63     if !default_arg_is_none {
64         // nothing to lint!
65         return;
66     }
67
68     let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind {
69         is_lang_ctor(cx, qpath, OptionSome)
70     } else {
71         false
72     };
73
74     if is_option {
75         let self_snippet = snippet(cx, recv.span, "..");
76         if_chain! {
77         if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind;
78             let arg_snippet = snippet(cx, span, "..");
79             let body = cx.tcx.hir().body(id);
80                 if let Some((func, [arg_char])) = reduce_unit_expression(cx, &body.value);
81                 if let Some(id) = path_def_id(cx, func).and_then(|ctor_id| cx.tcx.parent(ctor_id));
82                 if Some(id) == cx.tcx.lang_items().option_some_variant();
83                 then {
84                     let func_snippet = snippet(cx, arg_char.span, "..");
85                     let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
86                        `map(..)` instead";
87                     return span_lint_and_sugg(
88                         cx,
89                         OPTION_MAP_OR_NONE,
90                         expr.span,
91                         msg,
92                         "try using `map` instead",
93                         format!("{0}.map({1} {2})", self_snippet, arg_snippet,func_snippet),
94                         Applicability::MachineApplicable,
95                     );
96                 }
97
98         }
99
100         let func_snippet = snippet(cx, map_arg.span, "..");
101         let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
102                        `and_then(..)` instead";
103         return span_lint_and_sugg(
104             cx,
105             OPTION_MAP_OR_NONE,
106             expr.span,
107             msg,
108             "try using `and_then` instead",
109             format!("{0}.and_then({1})", self_snippet, func_snippet),
110             Applicability::MachineApplicable,
111         );
112     } else if f_arg_is_some {
113         let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \
114                        `ok()` instead";
115         let self_snippet = snippet(cx, recv.span, "..");
116         return span_lint_and_sugg(
117             cx,
118             RESULT_MAP_OR_INTO_OPTION,
119             expr.span,
120             msg,
121             "try using `ok` instead",
122             format!("{0}.ok()", self_snippet),
123             Applicability::MachineApplicable,
124         );
125     }
126 }