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