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