]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/methods/option_map_unwrap_or.rs
Merge commit 'f51aade56f93175dde89177a92e3669ebd8e7592' into clippyup
[rust.git] / src / tools / clippy / clippy_lints / src / methods / option_map_unwrap_or.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::source::snippet_with_applicability;
3 use clippy_utils::ty::is_copy;
4 use clippy_utils::ty::is_type_diagnostic_item;
5 use rustc_data_structures::fx::FxHashSet;
6 use rustc_errors::Applicability;
7 use rustc_hir::intravisit::{walk_path, Visitor};
8 use rustc_hir::{self, HirId, Path};
9 use rustc_lint::LateContext;
10 use rustc_middle::hir::nested_filter;
11 use rustc_span::source_map::Span;
12 use rustc_span::{sym, Symbol};
13
14 use super::MAP_UNWRAP_OR;
15
16 /// lint use of `map().unwrap_or()` for `Option`s
17 pub(super) fn check<'tcx>(
18     cx: &LateContext<'tcx>,
19     expr: &rustc_hir::Expr<'_>,
20     recv: &rustc_hir::Expr<'_>,
21     map_arg: &'tcx rustc_hir::Expr<'_>,
22     unwrap_recv: &rustc_hir::Expr<'_>,
23     unwrap_arg: &'tcx rustc_hir::Expr<'_>,
24     map_span: Span,
25 ) {
26     // lint if the caller of `map()` is an `Option`
27     if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option) {
28         if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) {
29             // Do not lint if the `map` argument uses identifiers in the `map`
30             // argument that are also used in the `unwrap_or` argument
31
32             let mut unwrap_visitor = UnwrapVisitor {
33                 cx,
34                 identifiers: FxHashSet::default(),
35             };
36             unwrap_visitor.visit_expr(unwrap_arg);
37
38             let mut map_expr_visitor = MapExprVisitor {
39                 cx,
40                 identifiers: unwrap_visitor.identifiers,
41                 found_identifier: false,
42             };
43             map_expr_visitor.visit_expr(map_arg);
44
45             if map_expr_visitor.found_identifier {
46                 return;
47             }
48         }
49
50         if unwrap_arg.span.ctxt() != map_span.ctxt() {
51             return;
52         }
53
54         let mut applicability = Applicability::MachineApplicable;
55         // get snippet for unwrap_or()
56         let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability);
57         // lint message
58         // comparing the snippet from source to raw text ("None") below is safe
59         // because we already have checked the type.
60         let arg = if unwrap_snippet == "None" { "None" } else { "<a>" };
61         let unwrap_snippet_none = unwrap_snippet == "None";
62         let suggest = if unwrap_snippet_none {
63             "and_then(<f>)"
64         } else {
65             "map_or(<a>, <f>)"
66         };
67         let msg = &format!(
68             "called `map(<f>).unwrap_or({})` on an `Option` value. \
69             This can be done more directly by calling `{}` instead",
70             arg, suggest
71         );
72
73         span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| {
74             let map_arg_span = map_arg.span;
75
76             let mut suggestion = vec![
77                 (
78                     map_span,
79                     String::from(if unwrap_snippet_none { "and_then" } else { "map_or" }),
80                 ),
81                 (expr.span.with_lo(unwrap_recv.span.hi()), String::new()),
82             ];
83
84             if !unwrap_snippet_none {
85                 suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{}, ", unwrap_snippet)));
86             }
87
88             diag.multipart_suggestion(&format!("use `{}` instead", suggest), suggestion, applicability);
89         });
90     }
91 }
92
93 struct UnwrapVisitor<'a, 'tcx> {
94     cx: &'a LateContext<'tcx>,
95     identifiers: FxHashSet<Symbol>,
96 }
97
98 impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> {
99     type NestedFilter = nested_filter::All;
100
101     fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
102         self.identifiers.insert(ident(path));
103         walk_path(self, path);
104     }
105
106     fn nested_visit_map(&mut self) -> Self::Map {
107         self.cx.tcx.hir()
108     }
109 }
110
111 struct MapExprVisitor<'a, 'tcx> {
112     cx: &'a LateContext<'tcx>,
113     identifiers: FxHashSet<Symbol>,
114     found_identifier: bool,
115 }
116
117 impl<'a, 'tcx> Visitor<'tcx> for MapExprVisitor<'a, 'tcx> {
118     type NestedFilter = nested_filter::All;
119
120     fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
121         if self.identifiers.contains(&ident(path)) {
122             self.found_identifier = true;
123             return;
124         }
125         walk_path(self, path);
126     }
127
128     fn nested_visit_map(&mut self) -> Self::Map {
129         self.cx.tcx.hir()
130     }
131 }
132
133 fn ident(path: &Path<'_>) -> Symbol {
134     path.segments
135         .last()
136         .expect("segments should be composed of at least 1 element")
137         .ident
138         .name
139 }