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