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