]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/map_unwrap_or.rs
Auto merge of #7546 - mgeier:patch-1, r=giraffate
[rust.git] / clippy_lints / src / methods / map_unwrap_or.rs
1 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
2 use clippy_utils::source::snippet;
3 use clippy_utils::ty::is_type_diagnostic_item;
4 use clippy_utils::usage::mutated_variables;
5 use clippy_utils::{meets_msrv, msrvs};
6 use rustc_errors::Applicability;
7 use rustc_hir as hir;
8 use rustc_lint::LateContext;
9 use rustc_semver::RustcVersion;
10 use rustc_span::symbol::sym;
11
12 use super::MAP_UNWRAP_OR;
13
14 /// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
15 /// Return true if lint triggered
16 pub(super) fn check<'tcx>(
17     cx: &LateContext<'tcx>,
18     expr: &'tcx hir::Expr<'_>,
19     recv: &'tcx hir::Expr<'_>,
20     map_arg: &'tcx hir::Expr<'_>,
21     unwrap_arg: &'tcx hir::Expr<'_>,
22     msrv: Option<&RustcVersion>,
23 ) -> bool {
24     // lint if the caller of `map()` is an `Option`
25     let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::option_type);
26     let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::result_type);
27
28     if is_result && !meets_msrv(msrv, &msrvs::RESULT_MAP_OR_ELSE) {
29         return false;
30     }
31
32     if is_option || is_result {
33         // Don't make a suggestion that may fail to compile due to mutably borrowing
34         // the same variable twice.
35         let map_mutated_vars = mutated_variables(recv, cx);
36         let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx);
37         if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
38             if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
39                 return false;
40             }
41         } else {
42             return false;
43         }
44
45         // lint message
46         let msg = if is_option {
47             "called `map(<f>).unwrap_or_else(<g>)` on an `Option` value. This can be done more directly by calling \
48             `map_or_else(<g>, <f>)` instead"
49         } else {
50             "called `map(<f>).unwrap_or_else(<g>)` on a `Result` value. This can be done more directly by calling \
51             `.map_or_else(<g>, <f>)` instead"
52         };
53         // get snippets for args to map() and unwrap_or_else()
54         let map_snippet = snippet(cx, map_arg.span, "..");
55         let unwrap_snippet = snippet(cx, unwrap_arg.span, "..");
56         // lint, with note if neither arg is > 1 line and both map() and
57         // unwrap_or_else() have the same span
58         let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1;
59         let same_span = map_arg.span.ctxt() == unwrap_arg.span.ctxt();
60         if same_span && !multiline {
61             let var_snippet = snippet(cx, recv.span, "..");
62             span_lint_and_sugg(
63                 cx,
64                 MAP_UNWRAP_OR,
65                 expr.span,
66                 msg,
67                 "try this",
68                 format!("{}.map_or_else({}, {})", var_snippet, unwrap_snippet, map_snippet),
69                 Applicability::MachineApplicable,
70             );
71             return true;
72         } else if same_span && multiline {
73             span_lint(cx, MAP_UNWRAP_OR, expr.span, msg);
74             return true;
75         }
76     }
77
78     false
79 }