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